Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bff9c7fc2 | ||
|
|
a211b0675d | ||
|
|
a3c27a23b9 | ||
|
|
8478822f4a | ||
|
|
d41e1f84a0 | ||
|
|
b771de5e80 | ||
|
|
d9bedf51a9 | ||
|
|
ee9c2ce4a4 | ||
|
|
f47eb88980 | ||
|
|
4dad153d28 | ||
|
|
5bfb381736 | ||
|
|
56d4697981 | ||
|
|
1a2cf014b6 | ||
|
|
21c03f72d6 | ||
|
|
831bf02a5c |
@@ -43,12 +43,4 @@ app.*.map.json
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Place unity project here
|
||||
/unity
|
||||
|
||||
# flutter will place unity Library dependency here
|
||||
/unityLibrary
|
||||
|
||||
# unityLibrary will place build here
|
||||
/android/unityLibrary
|
||||
pubspec.lock
|
||||
|
||||
@@ -1,239 +1,16 @@
|
||||
# Furikake App
|
||||
# furibase
|
||||
|
||||
Project ini adalah aplikasi mobile Furikake.
|
||||
A new Flutter project.
|
||||
|
||||
## Konfiguras dan perubahan android app
|
||||
## Getting Started
|
||||
|
||||
Edit android/app/src/main/kotlin ... /MainActivity.kt
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
```
|
||||
package com.paj.freekake
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity;
|
||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
||||
|
||||
//class MainActivity : FlutterActivity()
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
```
|
||||
|
||||
Gunakan konfigurasi ini pada file android/settings.gradle.kts
|
||||
|
||||
```
|
||||
pluginManagement {
|
||||
val flutterSdkPath = run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
include(":unityLibrary")
|
||||
project(":unityLibrary").projectDir = file("./unityLibrary")
|
||||
|
||||
include(":unityLibrary:xrmanifest.androidlib")
|
||||
//project(":unityLibrary:xrmanifest.androidlib").projectDir = file("unityLibrary/xrmanifest.androidlib")
|
||||
|
||||
//include(":unityLibrary:mobilenotifications.androidlib")
|
||||
//project(":unityLibrary:mobilenotifications.androidlib").projectDir = file("unityLibrary/mobilenotifications.androidlib")
|
||||
|
||||
```
|
||||
|
||||
Gunakan konfigurasi ini pada android/app/build.gradle.kts
|
||||
|
||||
```
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.paj.freekake"
|
||||
//compileSdk = flutter.compileSdkVersion
|
||||
compileSdk = 35
|
||||
//ndkVersion = "27.0.12077973"
|
||||
//ndkVersion = flutter.ndkVersion
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.paj.freekake"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
//minSdk = flutter.minSdkVersion
|
||||
//targetSdk = flutter.targetSdkVersion
|
||||
//minSdk = 24 // Set to 24 or higher
|
||||
minSdk = 30
|
||||
targetSdk = 35
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":unityLibrary"))
|
||||
implementation(project(":flutter_unity_widget"))
|
||||
}
|
||||
```
|
||||
|
||||
## Konfigurasi UnityLibrary
|
||||
|
||||
Gunakan konfigurasi ini pada file android/unityLibrary/build.gradle:
|
||||
|
||||
```
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(name: 'unity-classes', ext:'jar')
|
||||
implementation(name: 'arcore_client', ext:'aar')
|
||||
implementation(name: 'ARPresto', ext:'aar')
|
||||
implementation(name: 'UnityARCore', ext:'aar')
|
||||
implementation(name: 'unityandroidpermissions', ext:'aar')
|
||||
implementation project('xrmanifest.androidlib')
|
||||
//implementation project('mobilenotifications.androidlib')
|
||||
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "com.unity3d.player"
|
||||
// ndkPath "/Applications/Unity/Hub/Editor/2022.3.60f1/PlaybackEngines/AndroidPlayer/NDK"
|
||||
compileSdkVersion 35
|
||||
buildToolsVersion '34.0.0'
|
||||
ndkVersion "23.1.7779620"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 30
|
||||
targetSdkVersion 35
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
versionCode 1
|
||||
versionName '1.0.2'
|
||||
consumerProguardFiles 'proguard-unity.txt'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
noCompress = ['.unity3d', '.ress', '.resource', '.obb', '.bundle', '.unityexp']
|
||||
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
doNotStrip '*/armeabi-v7a/*.so'
|
||||
doNotStrip '*/arm64-v8a/*.so'
|
||||
jniLibs {
|
||||
useLegacyPackaging true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getSdkDir() {
|
||||
Properties local = new Properties()
|
||||
local.load(new FileInputStream("${rootDir}/local.properties"))
|
||||
return local.getProperty('sdk.dir')
|
||||
}
|
||||
|
||||
def BuildIl2Cpp(String workingDir, String configuration, String architecture, String abi, String[] staticLibraries) {
|
||||
def commandLineArgs = []
|
||||
commandLineArgs.add("--compile-cpp")
|
||||
commandLineArgs.add("--platform=Android")
|
||||
commandLineArgs.add("--architecture=" + architecture)
|
||||
commandLineArgs.add("--outputpath=" + workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.so")
|
||||
commandLineArgs.add("--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi)
|
||||
commandLineArgs.add("--incremental-g-c-time-slice=3")
|
||||
commandLineArgs.add("--dotnetprofile=unityaot-linux")
|
||||
commandLineArgs.add("--enable-debugger")
|
||||
commandLineArgs.add("--profiler-report")
|
||||
commandLineArgs.add("--profiler-output-file=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_conv.traceevents")
|
||||
commandLineArgs.add("--print-command-line")
|
||||
commandLineArgs.add("--data-folder=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput/data")
|
||||
commandLineArgs.add("--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput")
|
||||
commandLineArgs.add("--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache")
|
||||
commandLineArgs.add("--tool-chain-path=" + android.ndkDirectory)
|
||||
staticLibraries.eachWithIndex {fileName, i->
|
||||
commandLineArgs.add("--additional-libraries=" + workingDir + "/src/main/jniStaticLibs/" + abi + "/" + fileName)
|
||||
}
|
||||
def executableExtension = ""
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||
executableExtension = ".exe"
|
||||
commandLineArgs = commandLineArgs*.replace('\"', '\\\"')
|
||||
}
|
||||
exec {
|
||||
executable workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/il2cpp" + executableExtension
|
||||
args commandLineArgs
|
||||
environment "ANDROID_SDK_ROOT", getSdkDir()
|
||||
}
|
||||
delete workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.sym.so"
|
||||
ant.move(file: workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
|
||||
}
|
||||
|
||||
android {
|
||||
task BuildIl2CppTask {
|
||||
doLast {
|
||||
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), 'Debug', 'armv7', 'armeabi-v7a', [ ] as String[]);
|
||||
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), 'Debug', 'arm64', 'arm64-v8a', [ ] as String[]);
|
||||
}
|
||||
}
|
||||
afterEvaluate {
|
||||
if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders'))
|
||||
project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask
|
||||
if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders'))
|
||||
project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
jni.srcDirs = ["src/main/Il2CppOutputProject"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
@@ -9,28 +9,26 @@ android {
|
||||
namespace = "com.example.furibase"
|
||||
//compileSdk = flutter.compileSdkVersion
|
||||
compileSdk = 35
|
||||
//ndkVersion = "27.0.12077973"
|
||||
ndkVersion = "27.0.12077973"
|
||||
//ndkVersion = flutter.ndkVersion
|
||||
ndkVersion = "23.1.7779620"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.paj.freekake"
|
||||
applicationId = "com.example.furibase"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
//minSdk = flutter.minSdkVersion
|
||||
//targetSdk = flutter.targetSdkVersion
|
||||
//minSdk = 24 // Set to 24 or higher
|
||||
minSdk = 30
|
||||
minSdk = 24 // Set to 24 or higher
|
||||
targetSdk = 35
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
@@ -43,13 +41,12 @@ android {
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("com.google.android.material:material:1.6.1")
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":unityLibrary"))
|
||||
implementation(project(":flutter_unity_widget"))
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
<application
|
||||
android:label="furibase"
|
||||
android:name="${applicationName}"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@@ -11,7 +13,8 @@
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:screenOrientation="portrait">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
@@ -44,4 +47,6 @@
|
||||
</queries>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||
</manifest>
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.example.furibase
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity;
|
||||
|
||||
//class MainActivity : FlutterActivity()
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
class MainActivity : FlutterActivity()
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true"/>
|
||||
</network-security-config>
|
||||
@@ -1,11 +1,5 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs(
|
||||
file("${project(":unityLibrary").projectDir}/libs")
|
||||
)
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -18,39 +12,6 @@ subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
|
||||
subprojects {
|
||||
afterEvaluate {
|
||||
if (project.extensions.findByName("android") != null) {
|
||||
extensions.configure<com.android.build.gradle.BaseExtension>("android") {
|
||||
if (namespace == null) {
|
||||
namespace = project.group.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (plugins.hasPlugin("com.android.application") || plugins.hasPlugin("com.android.library")) {
|
||||
if (name == "flutter_unity_widget") {
|
||||
extensions.configure<com.android.build.gradle.BaseExtension>("android") {
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
(this as ExtensionAware).extensions.configure<org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions>("kotlinOptions") {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
if (namespace == null) {
|
||||
namespace = project.group.toString()
|
||||
}
|
||||
|
||||
compileSdkVersion(35)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
|
||||
@@ -18,30 +18,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.9.0" apply false
|
||||
id("com.android.application") version "8.7.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
||||
include(":unityLibrary")
|
||||
project(":unityLibrary").projectDir = file("./unityLibrary")
|
||||
|
||||
include(":unityLibrary:xrmanifest.androidlib")
|
||||
// project(":unityLibrary:xrmanifest.androidlib").projectDir = file("unityLibrary/xrmanifest.androidlib")
|
||||
|
||||
// include(":unityLibrary:mobilenotifications.androidlib")
|
||||
// project(":unityLibrary:mobilenotifications.androidlib").projectDir = file("unityLibrary/mobilenotifications.androidlib")
|
||||
|
||||
|
||||
|
||||
include(":unityLibrary")
|
||||
project(":unityLibrary").projectDir = file("./unityLibrary")
|
||||
|
||||
|
||||
include(":unityLibrary")
|
||||
project(":unityLibrary").projectDir = file("./unityLibrary")
|
||||
|
||||
|
||||
include(":unityLibrary")
|
||||
project(":unityLibrary").projectDir = file("./unityLibrary")
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation(name: 'unity-classes', ext:'jar')
|
||||
implementation(name: 'arcore_client', ext:'aar')
|
||||
implementation(name: 'ARPresto', ext:'aar')
|
||||
implementation(name: 'UnityARCore', ext:'aar')
|
||||
implementation(name: 'unityandroidpermissions', ext:'aar')
|
||||
implementation project('xrmanifest.androidlib')
|
||||
//implementation project('mobilenotifications.androidlib')
|
||||
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "com.unity3d.player"
|
||||
// ndkPath "/Applications/Unity/Hub/Editor/2022.3.60f1/PlaybackEngines/AndroidPlayer/NDK"
|
||||
compileSdkVersion 35
|
||||
buildToolsVersion '34.0.0'
|
||||
ndkVersion "23.1.7779620"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 30
|
||||
targetSdkVersion 35
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
versionCode 1
|
||||
versionName '1.0.2'
|
||||
consumerProguardFiles 'proguard-unity.txt'
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
noCompress = ['.unity3d', '.ress', '.resource', '.obb', '.bundle', '.unityexp']
|
||||
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
doNotStrip '*/armeabi-v7a/*.so'
|
||||
doNotStrip '*/arm64-v8a/*.so'
|
||||
jniLibs {
|
||||
useLegacyPackaging true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getSdkDir() {
|
||||
Properties local = new Properties()
|
||||
local.load(new FileInputStream("${rootDir}/local.properties"))
|
||||
return local.getProperty('sdk.dir')
|
||||
}
|
||||
|
||||
def BuildIl2Cpp(String workingDir, String configuration, String architecture, String abi, String[] staticLibraries) {
|
||||
def commandLineArgs = []
|
||||
commandLineArgs.add("--compile-cpp")
|
||||
commandLineArgs.add("--platform=Android")
|
||||
commandLineArgs.add("--architecture=" + architecture)
|
||||
commandLineArgs.add("--outputpath=" + workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.so")
|
||||
commandLineArgs.add("--baselib-directory=" + workingDir + "/src/main/jniStaticLibs/" + abi)
|
||||
commandLineArgs.add("--incremental-g-c-time-slice=3")
|
||||
commandLineArgs.add("--dotnetprofile=unityaot-linux")
|
||||
commandLineArgs.add("--enable-debugger")
|
||||
commandLineArgs.add("--profiler-report")
|
||||
commandLineArgs.add("--profiler-output-file=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_conv.traceevents")
|
||||
commandLineArgs.add("--print-command-line")
|
||||
commandLineArgs.add("--data-folder=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput/data")
|
||||
commandLineArgs.add("--generatedcppdir=" + workingDir + "/src/main/Il2CppOutputProject/Source/il2cppOutput")
|
||||
commandLineArgs.add("--cachedirectory=" + workingDir + "/build/il2cpp_"+ abi + "_" + configuration + "/il2cpp_cache")
|
||||
commandLineArgs.add("--tool-chain-path=" + android.ndkDirectory)
|
||||
staticLibraries.eachWithIndex {fileName, i->
|
||||
commandLineArgs.add("--additional-libraries=" + workingDir + "/src/main/jniStaticLibs/" + abi + "/" + fileName)
|
||||
}
|
||||
def executableExtension = ""
|
||||
if (org.gradle.internal.os.OperatingSystem.current().isWindows()) {
|
||||
executableExtension = ".exe"
|
||||
commandLineArgs = commandLineArgs*.replace('\"', '\\\"')
|
||||
}
|
||||
exec {
|
||||
executable workingDir + "/src/main/Il2CppOutputProject/IL2CPP/build/deploy/il2cpp" + executableExtension
|
||||
args commandLineArgs
|
||||
environment "ANDROID_SDK_ROOT", getSdkDir()
|
||||
}
|
||||
delete workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.sym.so"
|
||||
ant.move(file: workingDir + "/src/main/jniLibs/" + abi + "/libil2cpp.dbg.so", tofile: workingDir + "/symbols/" + abi + "/libil2cpp.so")
|
||||
}
|
||||
|
||||
android {
|
||||
task BuildIl2CppTask {
|
||||
doLast {
|
||||
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), 'Debug', 'armv7', 'armeabi-v7a', [ ] as String[]);
|
||||
BuildIl2Cpp(projectDir.toString().replaceAll('\\\\', '/'), 'Debug', 'arm64', 'arm64-v8a', [ ] as String[]);
|
||||
}
|
||||
}
|
||||
afterEvaluate {
|
||||
if (project(':unityLibrary').tasks.findByName('mergeDebugJniLibFolders'))
|
||||
project(':unityLibrary').mergeDebugJniLibFolders.dependsOn BuildIl2CppTask
|
||||
if (project(':unityLibrary').tasks.findByName('mergeReleaseJniLibFolders'))
|
||||
project(':unityLibrary').mergeReleaseJniLibFolders.dependsOn BuildIl2CppTask
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
jni.srcDirs = ["src/main/Il2CppOutputProject"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Game Nusantara</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
.container {
|
||||
max-width: 480px;
|
||||
margin: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.letter {
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
padding: 0;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
border: 1px solid #ccc;
|
||||
aspect-ratio: 1/1;
|
||||
}
|
||||
.found {
|
||||
background-color: #a2f5a2 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 id="game-title">Game Nusantara</h1>
|
||||
<div id="game-board"></div>
|
||||
|
||||
<h3>Kata yang harus ditemukan:</h3>
|
||||
<ul id="target-words" style="padding-left: 1rem;"></ul>
|
||||
<p id="penjelasan" style="margin-top: 1rem; font-size: 0.9rem; color: #444;"></p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Template Find The Word Game (drag to select)
|
||||
const words = ['TENANG', 'SABAR', 'SYUKUR', 'CINTA', 'GEMBIRA', 'BANGGA'];
|
||||
const board = document.getElementById('game-board');
|
||||
const size = 12; // 12x12 grid
|
||||
let grid = Array(size * size).fill('');
|
||||
|
||||
function randomLetter() {
|
||||
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
return alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||
}
|
||||
|
||||
function placeWord(word) {
|
||||
const dir = Math.random() < 0.5 ? 'H' : 'V';
|
||||
let x, y, success = false;
|
||||
while (!success) {
|
||||
x = Math.floor(Math.random() * (dir === 'H' ? size - word.length : size));
|
||||
y = Math.floor(Math.random() * (dir === 'V' ? size - word.length : size));
|
||||
let fits = true;
|
||||
for (let i = 0; i < word.length; i++) {
|
||||
let idx = dir === 'H' ? y * size + (x + i) : (y + i) * size + x;
|
||||
if (grid[idx] && grid[idx] !== word[i]) {
|
||||
fits = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (fits) {
|
||||
for (let i = 0; i < word.length; i++) {
|
||||
let idx = dir === 'H' ? y * size + (x + i) : (y + i) * size + x;
|
||||
grid[idx] = word[i];
|
||||
}
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
words.forEach(w => placeWord(w));
|
||||
grid = grid.map(cell => cell || randomLetter());
|
||||
|
||||
function renderGrid() {
|
||||
board.className = 'grid';
|
||||
board.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
|
||||
board.innerHTML = '';
|
||||
grid.forEach((char, i) => {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = char;
|
||||
div.className = 'letter';
|
||||
div.dataset.index = i;
|
||||
div.style.aspectRatio = '1/1';
|
||||
div.style.border = '1px solid #ccc';
|
||||
div.style.display = 'flex';
|
||||
div.style.alignItems = 'center';
|
||||
div.style.justifyContent = 'center';
|
||||
div.style.fontSize = '1.2rem';
|
||||
div.style.fontWeight = 'bold';
|
||||
div.style.userSelect = 'none';
|
||||
div.style.cursor = 'pointer';
|
||||
board.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
renderGrid();
|
||||
|
||||
// Daftar kata yang harus ditemukan
|
||||
// (Daftar kata tidak lagi ditampilkan sebagai list)
|
||||
|
||||
let currentSelection = [];
|
||||
let currentWord = '';
|
||||
let wordsFound = new Set();
|
||||
let isMouseDown = false;
|
||||
|
||||
board.addEventListener('mousedown', (e) => {
|
||||
if (!e.target.classList.contains('letter')) return;
|
||||
isMouseDown = true;
|
||||
selectCell(e.target);
|
||||
});
|
||||
|
||||
board.addEventListener('mouseover', (e) => {
|
||||
if (!isMouseDown || !e.target.classList.contains('letter')) return;
|
||||
selectCell(e.target);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
if (words.includes(currentWord)) {
|
||||
currentSelection.forEach(c => {
|
||||
c.style.backgroundColor = '#a2f5a2';
|
||||
c.classList.add('found');
|
||||
});
|
||||
const penjelasanBox = document.getElementById('penjelasan');
|
||||
const regex = new RegExp(currentWord, 'gi');
|
||||
penjelasanBox.innerHTML = penjelasanBox.innerHTML.replace(regex, `<span style="color:green; font-weight:bold">${currentWord}</span>`);
|
||||
|
||||
wordsFound.add(currentWord);
|
||||
if (wordsFound.size === words.length) {
|
||||
alert('Selamat! Kamu telah menemukan semua kata positif. Ingat, berpikir positif setiap hari bisa membuat hidupmu lebih ceria dan penuh semangat 🌈');
|
||||
}
|
||||
} else {
|
||||
currentSelection.forEach(c => c.style.backgroundColor = '');
|
||||
}
|
||||
currentSelection = [];
|
||||
currentWord = '';
|
||||
isMouseDown = false;
|
||||
});
|
||||
|
||||
function selectCell(cell) {
|
||||
if (cell.classList.contains('found') || currentSelection.includes(cell)) return;
|
||||
cell.style.backgroundColor = '#ffeaa7';
|
||||
currentSelection.push(cell);
|
||||
currentWord += cell.textContent;
|
||||
}
|
||||
|
||||
// Penjelasan pentingnya emosi positif
|
||||
const penjelasan = `Di dalam hidup, kita bisa merasa banyak hal. Tapi tahukah kamu? Emosi positif seperti <strong>tenang</strong>, <strong>sabar</strong>, <strong>syukur</strong>, <strong>cinta</strong>, <strong>gembira</strong>, dan <strong>bangga</strong> adalah kunci untuk hidup yang sehat dan bahagia. Yuk, temukan kata-kata positif itu di puzzle, lalu pikirkan kapan terakhir kali kamu merasakannya!`;
|
||||
document.getElementById('penjelasan').innerHTML = penjelasan;
|
||||
|
||||
function includeScript(file) {
|
||||
const script = document.createElement('script');
|
||||
script.src = file;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Test</title>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
document.body.innerHTML += '<p style="color:green;">✅ JS is working!</p>';
|
||||
};
|
||||
window.onload = showAlert;
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello WebView</h1>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,279 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Furikake: Taburan Lezat untuk Nasi!</title>
|
||||
<style>
|
||||
/* Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #eee;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 10px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.6rem;
|
||||
margin: 30px 0 15px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.div-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.div-item {
|
||||
text-align: center;
|
||||
flex: 1 0 calc(25% - 15px); /* 4 items per row by default */
|
||||
min-width: calc(25% - 15px);
|
||||
max-width: calc(25% - 15px);
|
||||
}
|
||||
|
||||
.div-item img {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.div-item p {
|
||||
margin-top: 8px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.full-width-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Responsive Adjustments */
|
||||
|
||||
/* Tablet Size (768px and below) - 2 items per row */
|
||||
@media (max-width: 768px) {
|
||||
.div-item {
|
||||
flex: 1 0 calc(50% - 15px);
|
||||
min-width: calc(50% - 15px);
|
||||
max-width: calc(50% - 15px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Size (480px and below) - 1 item per row */
|
||||
@media (max-width: 480px) {
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.japanese-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.div-item {
|
||||
flex: 1 0 100%;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Small Mobile (360px and below) - Adjust font sizes further */
|
||||
@media (max-width: 360px) {
|
||||
h1 {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Furikake: Taburan Lezat untuk Nasi!</h1>
|
||||
<p class="subtitle">Tahukah kamu bahwa di Jepang ada bumbu tabur yang bisa membuat nasi menjadi lebih enak?</p>
|
||||
</header>
|
||||
|
||||
<div class="japanese-title">ふりかけ</div>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<p>Kata "furikake" berasal dari bahasa Jepang, yaitu "furi" yang berarti menaburkan dan "kake" yang berarti menuangkan. Jadi, furikake adalah bumbu yang ditaburkan di atas nasi supaya lebih lezat!</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Asal Usul Furikake</h2>
|
||||
<p>Furikake pertama kali dibuat di Jepang pada tahun 1913 oleh seorang apoteker bernama Suekichi Yoshimaru. Awalnya, ia ingin membantu orang-orang yang kekurangan kalsium. Maka, ia membuat bubuk dari tulang ikan kering, biji wijen, dan rumput laut. Campuran ini kemudian diberi nama "Gohan no Tomo" yang artinya "Teman Nasi" karena memang cocok untuk dimakan bersama nasi.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Perkembangan Furikake</h2>
|
||||
<p>Seiring berjalannya waktu, furikake menjadi semakin populer dan dibuat dengan berbagai rasa. Dulu, furikake hanya bisa dibeli oleh orang kaya, tetapi setelah Perang Dunia II, furikake mulai dijual dengan harga terjangkau sehingga semua orang bisa menikmatinya. Akhirnya, pada tahun 1959, furikake semakin terkenal dan banyak orang mulai membuat berbagai variasi rasa yang lebih menarik.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Apa Saja Isi Furikake?</h2>
|
||||
<p>Furikake biasanya terbuat dari campuran beberapa bahan seperti:</p>
|
||||
|
||||
<div class="div-grid">
|
||||
<div class="div-item">
|
||||
<img src="nori.jpeg" alt="Nori">
|
||||
<p>Nori (Rumput Laut)</p>
|
||||
</div>
|
||||
<div class="div-item">
|
||||
<img src="wijen.jpeg" alt="Biji Wijen">
|
||||
<p>Biji Wijen</p>
|
||||
</div>
|
||||
<div class="div-item">
|
||||
<img src="ikankering.jpg" alt="Katsuobushi">
|
||||
<p>Katsuobushi (Ikan Kering)</p>
|
||||
</div>
|
||||
<div class="div-item">
|
||||
<img src="gulagaram.jpg" alt="Garam dan Gula">
|
||||
<p>Garam & Gula</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<li>Rumput laut kering (nori) yang memberikan rasa laut yang khas.</li>
|
||||
<li>Biji wijen yang menambah rasa gurih dan tekstur renyah.</li>
|
||||
<li>Katsuobushi (ikan kering) yang menambah rasa gurih dan protein.</li>
|
||||
<li>Gula dan garam untuk memberikan keseimbangan rasa.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Beragam Rasa Furikake</h2>
|
||||
<p>Sekarang, furikake punya banyak rasa yang bisa dipilih sesuai selera! Beberapa contoh rasa furikake adalah:</p>
|
||||
<ul>
|
||||
<li>Wasabi Furikake, untuk yang suka rasa pedas khas wasabi.</li>
|
||||
<li>Salmon Furikake, dengan aroma salmon yang lezat.</li>
|
||||
<li>Furikake Vegan, yang dibuat tanpa bahan dari hewan, cocok untuk vegetarian.</li>
|
||||
</ul>
|
||||
|
||||
<figure>
|
||||
<img src="furikake.jpg" alt="Contoh produk furikake" class="full-width-image">
|
||||
<figcaption>Berbagai varian produk furikake yang tersedia di pasaran</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Cara Menikmati Furikake</h2>
|
||||
<p>Furikake sangat mudah digunakan! Cukup taburkan di atas nasi hangat, dan nasi pun menjadi lebih lezat. Bisa juga ditambahkan ke dalam mie, sup, atau onigiri (nasi kepal) untuk menambah rasa.</p>
|
||||
<p>Nah, sekarang kamu tahu tentang furikake! Kalau suatu saat berkesempatan mencobanya, jangan lupa untuk menikmati kelezatannya. Siapa tahu, kamu jadi semakin suka makan nasi!</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Artikel tentang Furikake - Taburan Lezat untuk Nasi</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,243 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Makan Sehat dengan Gizi Seimbang!</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #eee;
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.2rem;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 10px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.6rem;
|
||||
margin: 30px 0 15px 0;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 8px;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.div-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.div-item {
|
||||
text-align: center;
|
||||
max-width: 100px;
|
||||
flex: 1 1 200px;
|
||||
}
|
||||
|
||||
.div-item img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.full-width-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.div-grid {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.div-item {
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>Makan Sehat dengan Gizi Seimbang!</h1>
|
||||
<p class="subtitle">Tahukah kamu bahwa makan dengan gizi seimbang itu penting untuk tubuh kita?</p>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<p>Gizi seimbang berarti kita harus makan berbagai jenis makanan dengan jumlah yang pas supaya tubuh tetap sehat dan kuat!</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Apa Itu Gizi Seimbang?</h2>
|
||||
<p>Gizi seimbang adalah cara makan yang mengandung semua zat gizi yang dibutuhkan tubuh. Zat gizi itu terdiri dari:</p>
|
||||
|
||||
<div class="div-grid">
|
||||
<div class="div-item">
|
||||
<img src="karbohidrat.png" alt="Karbohidrat" />
|
||||
<p><strong>Karbohidrat</strong><br>Sumber energi seperti nasi, roti, dan kentang.</p>
|
||||
</div>
|
||||
<div class="div-item">
|
||||
<img src="protein.png" alt="Protein" />
|
||||
<p><strong>Protein</strong><br>Untuk membangun otot, misalnya ikan, telur, dan tahu.</p>
|
||||
</div>
|
||||
<div class="div-item">
|
||||
<img src="lemak.png" alt="Lemak" />
|
||||
<p><strong>Lemak</strong><br>Memberikan tenaga, seperti dari kacang-kacangan dan minyak sehat.</p>
|
||||
</div>
|
||||
<div class="div-item">
|
||||
<img src="vitaminmineral.png" alt="Vitamin dan Mineral" />
|
||||
<p><strong>Vitamin dan mineral</strong><br>Dari sayur dan buah yang membantu tubuh bekerja dengan baik.</p>
|
||||
</div>
|
||||
<div class="div-item">
|
||||
<img src="seratair.png" alt="Serat dan Air" />
|
||||
<p><strong>Serat dan air</strong><br>Membantu pencernaan dan menjaga tubuh tetap segar!</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<h2>Porsi Makan yang Seimbang</h2>
|
||||
<p>Supaya makan kita sehat, ada aturan "Isi Piringku" yang bisa kita ikuti:</p>
|
||||
<ul>
|
||||
<li>🍽️ Separuh piring: Sayur dan buah</li>
|
||||
<li>🍽️ Seperempat piring: Karbohidrat (seperti nasi atau roti)</li>
|
||||
<li>🍽️ Seperempat piring: Protein (seperti ayam atau tempe)</li>
|
||||
<li>🍽️ Sedikit lemak sehat</li>
|
||||
</ul>
|
||||
<img src="isipiringku.png" alt="Piring Gizi Seimbang" class="full-width-image">
|
||||
<figcaption>Isi Piringku</figcaption>
|
||||
<p>Selain itu, kita tidak boleh makan terlalu banyak atau terlalu sedikit, karena bisa membuat tubuh tidak sehat. Makan berlebihan bisa menyebabkan obesitas, sedangkan makan terlalu sedikit bisa membuat tubuh lemas dan kurang gizi.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Menjaga Kebersihan Makanan</h2>
|
||||
<p>Selain memilih makanan sehat, kita juga harus menjaga kebersihan makanan agar tidak sakit perut! Caranya:</p>
|
||||
<ul>
|
||||
<li>Cuci tangan sebelum makan dan mengolah makanan.</li>
|
||||
<li>Simpan makanan dengan baik agar tidak terkena kuman.</li>
|
||||
<li>Masak makanan dengan suhu yang cukup supaya aman dimakan.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Aktivitas Fisik Itu Penting!</h2>
|
||||
<p>Selain makan sehat, kita juga harus rajin bergerak! Olahraga atau bermain di luar rumah bisa membantu tubuh tetap bugar, menjaga berat badan, dan membuat kita lebih bersemangat.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Yuk, Mulai Gizi Seimbang!</h2>
|
||||
<p>Makan dengan gizi seimbang akan membuat tubuh kita lebih sehat, kuat, dan penuh energi. Yuk, biasakan makan sehat mulai sekarang supaya kita tumbuh dengan baik dan terhindar dari penyakit!</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>Artikel tentang Makan Sehat dengan Gizi Seimbang</p>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,176 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Game Nusantara</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
margin: 0;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
.container {
|
||||
max-width: 480px;
|
||||
margin: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.grid, .drag-area, .drop-area {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||||
}
|
||||
.card, .letter, .drop-zone, .drag-item {
|
||||
background: white;
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 0.8rem;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
.hidden { background: #ccc; color: transparent; }
|
||||
.matched { background: #d4edda; }
|
||||
.drop-zone { height: 80px; background: #eee; border: 2px dashed #bbb; }
|
||||
.drag-item[draggable="true"] { cursor: grab; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1 id="game-title">Game Nusantara</h1>
|
||||
<div id="game-board"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const items = [
|
||||
'pempek.png', 'sumsel.png',
|
||||
'sopkonro.png', 'sulsel.png',
|
||||
'rendang.png', 'sumbar.png',
|
||||
'tinutuan.png', 'sulut.png',
|
||||
'ayambetutu.png', 'bali.png',
|
||||
'bikaambon.png', 'sumut.png',
|
||||
'gudeg.png', 'yogyakarta.png',
|
||||
'sotobanjar.png', 'kalsel.png',
|
||||
'ayamtaliwang.png', 'ntb.png'
|
||||
];
|
||||
|
||||
const board = document.getElementById('game-board');
|
||||
board.className = 'grid';
|
||||
board.style.gridTemplateColumns = 'repeat(3, 1fr)';
|
||||
|
||||
let selectedItems = shuffle([...items]);
|
||||
let shuffled = shuffle(selectedItems.slice(0, 18));
|
||||
let selected = [], matched = [];
|
||||
|
||||
function shuffle(array) {
|
||||
return array.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
|
||||
function render() {
|
||||
board.innerHTML = '';
|
||||
shuffled.forEach((img, i) => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card hidden';
|
||||
card.style.backgroundImage = 'url("../freekake.png")';
|
||||
card.style.backgroundSize = 'contain';
|
||||
card.style.backgroundRepeat = 'no-repeat';
|
||||
card.style.backgroundPosition = 'center';
|
||||
card.dataset.index = i;
|
||||
card.style.aspectRatio = '1/1';
|
||||
card.style.borderRadius = '12px';
|
||||
card.style.overflow = 'hidden';
|
||||
card.style.backgroundColor = '#fff';
|
||||
card.style.display = 'flex';
|
||||
card.style.flexDirection = 'column';
|
||||
card.style.alignItems = 'center';
|
||||
card.style.justifyContent = 'center';
|
||||
card.style.padding = '8px';
|
||||
card.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
|
||||
board.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
function revealCard(i) {
|
||||
const el = board.children[i];
|
||||
el.classList.remove('hidden');
|
||||
el.style.backgroundImage = 'none';
|
||||
el.innerHTML = `<img src="${shuffled[i]}" alt="" style="width: 100%; height: auto; object-fit: contain;">`;
|
||||
}
|
||||
|
||||
function hideCard(i) {
|
||||
const el = board.children[i];
|
||||
el.classList.add('hidden');
|
||||
el.style.backgroundImage = 'url("../freekake.png")';
|
||||
el.style.backgroundSize = 'contain';
|
||||
el.style.backgroundRepeat = 'no-repeat';
|
||||
el.style.backgroundPosition = 'center';
|
||||
el.innerHTML = '';
|
||||
}
|
||||
|
||||
function checkMatch() {
|
||||
const [a, b] = selected;
|
||||
const pairs = {
|
||||
'pempek.png': 'sumsel.png', 'sumsel.png': 'pempek.png',
|
||||
'sopkonro.png': 'sulsel.png', 'sulsel.png': 'sopkonro.png',
|
||||
'rendang.png': 'sumbar.png', 'sumbar.png': 'rendang.png',
|
||||
'tinutuan.png': 'sulut.png', 'sulut.png': 'tinutuan.png',
|
||||
'ayambetutu.png': 'bali.png', 'bali.png': 'ayambetutu.png',
|
||||
'bikaambon.png': 'sumut.png', 'sumut.png': 'bikaambon.png',
|
||||
'gudeg.png': 'yogyakarta.png', 'yogyakarta.png': 'gudeg.png',
|
||||
'sotobanjar.png': 'kalsel.png', 'kalsel.png': 'sotobanjar.png',
|
||||
'ayamtaliwang.png': 'ntb.png', 'ntb.png': 'ayamtaliwang.png'
|
||||
};
|
||||
if (pairs[shuffled[a]] === shuffled[b]) {
|
||||
matched.push(a, b);
|
||||
board.children[a].classList.add('matched');
|
||||
board.children[a].style.backgroundColor = '#d4edda';
|
||||
board.children[b].classList.add('matched');
|
||||
board.children[b].style.backgroundColor = '#d4edda';
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
hideCard(a);
|
||||
hideCard(b);
|
||||
}, 800);
|
||||
}
|
||||
selected = [];
|
||||
}
|
||||
|
||||
render();
|
||||
|
||||
board.addEventListener('click', (e) => {
|
||||
const index = e.target.closest('.card')?.dataset.index;
|
||||
if (!index || selected.includes(+index) || matched.includes(+index)) return;
|
||||
revealCard(index);
|
||||
selected.push(+index);
|
||||
if (selected.length === 2) checkMatch();
|
||||
});
|
||||
|
||||
window.render = render;
|
||||
|
||||
if (window.flutter_inappwebview) {
|
||||
window.flutter_inappwebview.callHandler('pageReady', {
|
||||
boardExists: !!document.getElementById("game-board"),
|
||||
hasRender: typeof render === "function"
|
||||
});
|
||||
}
|
||||
|
||||
// Optional helper
|
||||
function includeScript(file) {
|
||||
const script = document.createElement('script');
|
||||
script.src = file;
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 221 KiB |
@@ -1,9 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/navbar_container.dart';
|
||||
import 'package:freekake/components/navbar_shape_1.dart';
|
||||
import 'package:freekake/components/navbar_shape_2.dart';
|
||||
import 'package:freekake/components/navbar_shape_3.dart';
|
||||
import 'package:freekake/components/custom_shape.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BottomNavbar extends StatefulWidget {
|
||||
const BottomNavbar({super.key});
|
||||
@@ -13,6 +9,7 @@ class BottomNavbar extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _BottomNavbarState extends State<BottomNavbar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Size screenSize = MediaQuery.of(context).size;
|
||||
return CustomPaint(
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CardList extends StatelessWidget {
|
||||
const CardList({super.key});
|
||||
final String title;
|
||||
final String body;
|
||||
final String? gambar;
|
||||
final String? tipe;
|
||||
final String? point;
|
||||
final String? coint;
|
||||
|
||||
const CardList({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.body,
|
||||
this.gambar,
|
||||
this.tipe,
|
||||
this.point,
|
||||
this.coint,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -18,12 +33,20 @@ class CardList extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(
|
||||
10.0,
|
||||
), // Opsional: membuat gambar lebih rounded
|
||||
child: Image.asset(
|
||||
'assets/images/cepott.png', // Ganti dengan path gambar Anda
|
||||
width: 80, // Atur ukuran gambar
|
||||
height: 120,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
child:
|
||||
(gambar != null)
|
||||
? Image.asset(
|
||||
gambar!,
|
||||
width: 65, // Atur ukuran gambar
|
||||
height: 80,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.asset(
|
||||
'assets/images/cepott.png',
|
||||
width: 80, // Atur ukuran gambar
|
||||
height: 120,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
@@ -31,17 +54,18 @@ class CardList extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Judul Card",
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 5, width: 10),
|
||||
const SizedBox(height: 1, width: 5),
|
||||
Text(
|
||||
"Deskripsi singkat tentang item ini. karena ini sangat panjang maka ini akan dipotong",
|
||||
body,
|
||||
style: const TextStyle(
|
||||
fontSize: 12,
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
maxLines: 2,
|
||||
@@ -49,19 +73,71 @@ class CardList extends StatelessWidget {
|
||||
),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: const Row(
|
||||
padding: EdgeInsets.all(0.08),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Text("50"),
|
||||
Chip(
|
||||
labelPadding: EdgeInsets.symmetric(
|
||||
horizontal: 0,
|
||||
vertical: 0,
|
||||
),
|
||||
label: Text(
|
||||
point ?? ' 200',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.blue,
|
||||
avatar: const CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Icon(
|
||||
Icons.emoji_events,
|
||||
size: 16,
|
||||
color: Colors.amber,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 40),
|
||||
Text("100"),
|
||||
Chip(
|
||||
labelPadding: EdgeInsets.symmetric(
|
||||
horizontal: 0,
|
||||
vertical: 0,
|
||||
),
|
||||
label: Text(
|
||||
coint ?? " 100",
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.blue,
|
||||
avatar: const CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Icon(
|
||||
Icons.monetization_on,
|
||||
size: 12,
|
||||
color: Colors.amber,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text("Quiz"),
|
||||
Chip(
|
||||
labelPadding: EdgeInsets.symmetric(
|
||||
horizontal: 0,
|
||||
vertical: 0,
|
||||
),
|
||||
label: Text(
|
||||
tipe ?? "Text",
|
||||
style: TextStyle(color: Colors.white, fontSize: 14),
|
||||
),
|
||||
backgroundColor: Colors.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CharacterInfo extends StatelessWidget {
|
||||
final String nama;
|
||||
final String kelamin;
|
||||
final String asal;
|
||||
final String content;
|
||||
|
||||
const CharacterInfo({
|
||||
super.key,
|
||||
required this.nama,
|
||||
required this.kelamin,
|
||||
required this.asal,
|
||||
required this.content,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
"Nama Karakter",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
":",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
nama,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
"Jenis Kelamin",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
":",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
kelamin,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
"Asal Karakter",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
":",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
asal,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
content,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class CollectionContainer extends StatelessWidget {
|
||||
final bool? small;
|
||||
|
||||
const CollectionContainer({
|
||||
Key? key,
|
||||
super.key,
|
||||
this.label,
|
||||
this.imagesrc,
|
||||
this.imageSvg,
|
||||
@@ -31,7 +31,7 @@ class CollectionContainer extends StatelessWidget {
|
||||
this.circle,
|
||||
this.colorContiner,
|
||||
this.small,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CurvedBottomBorderTab extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final String label;
|
||||
final VoidCallback tap;
|
||||
|
||||
const CurvedBottomBorderTab({
|
||||
super.key,
|
||||
required this.isSelected,
|
||||
required this.label,
|
||||
required this.tap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomPaint(
|
||||
painter: isSelected ? CurvedBottomPainter() : null,
|
||||
child: GestureDetector(
|
||||
onTap: tap,
|
||||
child: Container(
|
||||
width: 110,
|
||||
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
color: Colors.transparent,
|
||||
child: Center(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color:
|
||||
isSelected
|
||||
? const Color.fromARGB(255, 255, 255, 255)
|
||||
: const Color.fromARGB(255, 197, 194, 194),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CurvedBottomPainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint =
|
||||
Paint()
|
||||
..color = Colors.white
|
||||
..strokeWidth = 2
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final path =
|
||||
Path()
|
||||
..moveTo(0, size.height)
|
||||
..quadraticBezierTo(
|
||||
size.width / 2,
|
||||
size.height - 8,
|
||||
size.width,
|
||||
size.height,
|
||||
);
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(CustomPainter oldDelegate) => false;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/navbar_shape_2.dart';
|
||||
// import 'package:freekake/components/navbar_shape_2.dart';
|
||||
|
||||
class CustomShape extends StatelessWidget {
|
||||
const CustomShape({super.key});
|
||||
@@ -23,7 +23,7 @@ class RPSCustomPainter extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// Layer 1
|
||||
|
||||
Paint paint_fill_0 =
|
||||
Paint paintFill0 =
|
||||
Paint()
|
||||
..color = const Color.fromARGB(255, 189, 18, 18)
|
||||
..style = PaintingStyle.fill
|
||||
@@ -67,11 +67,11 @@ class RPSCustomPainter extends CustomPainter {
|
||||
);
|
||||
path_0.close();
|
||||
|
||||
canvas.drawPath(path_0, paint_fill_0);
|
||||
canvas.drawPath(path_0, paintFill0);
|
||||
|
||||
// Layer 1
|
||||
|
||||
Paint paint_stroke_0 =
|
||||
Paint paintStroke0 =
|
||||
Paint()
|
||||
..color = const Color.fromARGB(255, 33, 150, 243)
|
||||
..style = PaintingStyle.stroke
|
||||
@@ -79,11 +79,11 @@ class RPSCustomPainter extends CustomPainter {
|
||||
..strokeCap = StrokeCap.butt
|
||||
..strokeJoin = StrokeJoin.miter;
|
||||
|
||||
canvas.drawPath(path_0, paint_stroke_0);
|
||||
canvas.drawPath(path_0, paintStroke0);
|
||||
|
||||
// Layer 1
|
||||
|
||||
Paint paint_fill_1 =
|
||||
Paint paintFill1 =
|
||||
Paint()
|
||||
..color = const Color.fromARGB(255, 28, 163, 52)
|
||||
..style = PaintingStyle.fill
|
||||
@@ -116,11 +116,11 @@ class RPSCustomPainter extends CustomPainter {
|
||||
);
|
||||
path_1.close();
|
||||
|
||||
canvas.drawPath(path_1, paint_fill_1);
|
||||
canvas.drawPath(path_1, paintFill1);
|
||||
|
||||
// Layer 1
|
||||
|
||||
Paint paint_stroke_1 =
|
||||
Paint paintStroke1 =
|
||||
Paint()
|
||||
..color = const Color.fromARGB(255, 33, 150, 243)
|
||||
..style = PaintingStyle.stroke
|
||||
@@ -128,7 +128,7 @@ class RPSCustomPainter extends CustomPainter {
|
||||
..strokeCap = StrokeCap.butt
|
||||
..strokeJoin = StrokeJoin.miter;
|
||||
|
||||
canvas.drawPath(path_1, paint_stroke_1);
|
||||
canvas.drawPath(path_1, paintStroke1);
|
||||
|
||||
// layer 2
|
||||
// Paint paint_fill_0 =
|
||||
@@ -164,7 +164,7 @@ class RPSCustomPainter extends CustomPainter {
|
||||
);
|
||||
path_0.close();
|
||||
|
||||
canvas.drawPath(path_0, paint_fill_0);
|
||||
canvas.drawPath(path_0, paintFill0);
|
||||
|
||||
// Layer 1
|
||||
|
||||
|
||||
@@ -1,72 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/bottom_navbar.dart';
|
||||
import 'package:freekake/components/menu_button.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/providers/menu_selection_provider.dart';
|
||||
import 'package:freekake/screen/Home_screen.dart';
|
||||
import 'package:freekake/screen/koleksi_screen.dart';
|
||||
import 'package:freekake/screen/pustaka_screen.dart';
|
||||
import 'package:freekake/screen/saya/profile_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MainMenu extends StatelessWidget {
|
||||
class MainMenu extends StatefulWidget {
|
||||
const MainMenu({super.key});
|
||||
@override
|
||||
_MainMenuState createState() => _MainMenuState();
|
||||
}
|
||||
|
||||
class _MainMenuState extends State<MainMenu> {
|
||||
int selectedIndex = 0;
|
||||
|
||||
final List<Widget> _screens = [
|
||||
HomeScreen(),
|
||||
KoleksiScreen(),
|
||||
PustakaScreen(),
|
||||
ProfileScreen(),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Size size = MediaQuery.of(context).size;
|
||||
final selectedIndex =
|
||||
Provider.of<MenuSelectionProvider>(context).selectedIndex;
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MenuButton(
|
||||
label: "E-furibuddy",
|
||||
icon: 'assets/icons/furrybuddy.svg',
|
||||
onPress:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => HomeScreen()),
|
||||
),
|
||||
},
|
||||
),
|
||||
MenuButton(
|
||||
label: "Koleksi",
|
||||
icon: 'assets/icons/Koleksi.svg',
|
||||
onPress:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => KoleksiScreen()),
|
||||
),
|
||||
},
|
||||
),
|
||||
SizedBox(width: 120),
|
||||
MenuButton(
|
||||
label: "Pustaka",
|
||||
icon: 'assets/icons/Pustaka.svg',
|
||||
onPress:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => PustakaScreen()),
|
||||
),
|
||||
},
|
||||
),
|
||||
MenuButton(
|
||||
label: "Saya",
|
||||
icon: 'assets/icons/Saya.svg',
|
||||
onPress:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => ProfileScreen()),
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 5),
|
||||
child: _btns(context, selectedIndex),
|
||||
);
|
||||
}
|
||||
|
||||
Row _btns(BuildContext context, int selectedIndex) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
MenuButton(
|
||||
label: "E-furibuddy",
|
||||
icon: 'assets/icons/furrybuddy.svg',
|
||||
onPress: () => _onItemTapped(context, 0),
|
||||
isSelected: selectedIndex == 0,
|
||||
),
|
||||
MenuButton(
|
||||
label: "Koleksi",
|
||||
icon: 'assets/icons/Koleksi.svg',
|
||||
onPress: () => _onItemTapped(context, 1),
|
||||
isSelected: selectedIndex == 1,
|
||||
),
|
||||
SizedBox(width: 100),
|
||||
MenuButton(
|
||||
label: "Pustaka",
|
||||
icon: 'assets/icons/Pustaka.svg',
|
||||
onPress: () => _onItemTapped(context, 2),
|
||||
isSelected: selectedIndex == 2,
|
||||
),
|
||||
MenuButton(
|
||||
label: "Saya",
|
||||
icon: 'assets/icons/Saya.svg',
|
||||
onPress: () => _onItemTapped(context, 3),
|
||||
isSelected: selectedIndex == 3,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onItemTapped(BuildContext context, int index) {
|
||||
// Mengubah selectedIndex di MenuSelectionProvider
|
||||
Provider.of<MenuSelectionProvider>(context, listen: false).selectedIndex =
|
||||
index;
|
||||
|
||||
// Pindah halaman sesuai index yang dipilih
|
||||
switch (index) {
|
||||
case 0:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => HomeScreen()),
|
||||
);
|
||||
break;
|
||||
case 1:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => KoleksiScreen()),
|
||||
);
|
||||
break;
|
||||
case 2:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => PustakaScreen()),
|
||||
);
|
||||
break;
|
||||
case 3:
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => ProfileScreen()),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,13 @@ class MenuButton extends StatelessWidget {
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
icon ?? '',
|
||||
width: w ?? 24,
|
||||
height: h ?? 24,
|
||||
width: w ?? 28,
|
||||
height: h ?? 28,
|
||||
// allowDrawingOutsideViewBox: true,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Color.fromARGB(255, 255, 255, 255),
|
||||
(isSelected ?? false)
|
||||
? Color.fromARGB(255, 216, 182, 10)
|
||||
: Color.fromARGB(255, 255, 255, 255),
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
@@ -41,9 +43,11 @@ class MenuButton extends StatelessWidget {
|
||||
Text(
|
||||
label ?? '',
|
||||
style: TextStyle(
|
||||
color: Color.fromARGB(255, 239, 224, 232),
|
||||
color:
|
||||
(isSelected ?? false)
|
||||
? Color.fromARGB(255, 216, 182, 10)
|
||||
: Color.fromARGB(255, 239, 224, 232),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MenuItem {
|
||||
const MenuItem({required this.text, required this.icon});
|
||||
final String text;
|
||||
final IconData icon;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/navbar_shape_2.dart';
|
||||
|
||||
class NavbarContainer extends StatelessWidget {
|
||||
const NavbarContainer({super.key});
|
||||
@@ -23,7 +22,7 @@ class MyShape extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// TODO: implement paint
|
||||
Paint paint =
|
||||
new Paint()
|
||||
Paint()
|
||||
..color = const Color.fromARGB(255, 166, 12, 66)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class BNBCustomPainter2 extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
debugPrint("Drawing navbar...");
|
||||
Paint paint =
|
||||
new Paint()
|
||||
Paint()
|
||||
..color = const Color.fromARGB(255, 79, 76, 182)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ class BNBCustomPainter3 extends CustomPainter {
|
||||
void paint(Canvas canvas, Size size) {
|
||||
debugPrint("Drawing navbar...");
|
||||
Paint paint =
|
||||
new Paint()
|
||||
Paint()
|
||||
..color = const Color.fromARGB(255, 79, 76, 182)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
@@ -17,7 +17,7 @@ class BNBCustomPainter3 extends CustomPainter {
|
||||
path.quadraticBezierTo(size.width * 0.40, 0, size.width * 0.40, 20);
|
||||
path.arcToPoint(
|
||||
Offset(size.width * 0.60, 20),
|
||||
radius: Radius.circular(20.0),
|
||||
radius: Radius.circular(size.height * 0.5),
|
||||
clockwise: false,
|
||||
);
|
||||
path.quadraticBezierTo(size.width * 0.60, 0, size.width * 0.63, 0);
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/providers/point_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PointsHeader extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final points = context.watch<PointsProvider>().points;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.all(12),
|
||||
color: const Color.fromARGB(99, 255, 255, 255),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Text(
|
||||
"Activity", //${points.xp}/${points.xpToNextLevel()}",
|
||||
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(
|
||||
"",
|
||||
// "🔥 Level: ${points.level}",
|
||||
// style: TextStyle(color: Colors.orangeAccent),
|
||||
),
|
||||
Text(
|
||||
"🏆",
|
||||
// "💎 GP: ${points.gp}",
|
||||
style: TextStyle(color: Colors.lightBlue),
|
||||
),
|
||||
Text(
|
||||
"🟡",
|
||||
// "🎯" SP: ${points.sp}",
|
||||
style: TextStyle(color: Colors.greenAccent),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,47 @@
|
||||
import 'package:freekake/screen/camera_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
// import 'package:freekake/screen/camera_screen.dart';
|
||||
|
||||
class ScanButton extends StatelessWidget {
|
||||
const ScanButton({super.key});
|
||||
static const Color transparent = Color(0xFFFFFFF);
|
||||
static const Color transparent = Color(0x0fffffff);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
|
||||
// Skala berdasarkan lebar layar
|
||||
final buttonSize = screenWidth * 0.20; // Adjust as needed
|
||||
final iconSize = buttonSize * 0.7;
|
||||
final padding = buttonSize * 0.15;
|
||||
|
||||
return Container(
|
||||
width: 86,
|
||||
height: 86,
|
||||
width: buttonSize,
|
||||
height: buttonSize,
|
||||
// width: 76,
|
||||
// height: 76,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: const Color.fromARGB(223, 67, 63, 179),
|
||||
),
|
||||
padding: EdgeInsets.all(10),
|
||||
padding: EdgeInsets.all(padding),
|
||||
child: IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
'assets/icons/Scan.svg',
|
||||
width: 60,
|
||||
height: 60,
|
||||
width: iconSize,
|
||||
height: iconSize,
|
||||
colorFilter: ColorFilter.mode(
|
||||
Color.fromARGB(255, 217, 219, 227),
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
),
|
||||
onPressed: () {},
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => CameraScreen()),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SkinInfo extends StatelessWidget {
|
||||
final String nama;
|
||||
final String asal;
|
||||
final String content;
|
||||
|
||||
const SkinInfo({
|
||||
super.key,
|
||||
required this.nama,
|
||||
required this.asal,
|
||||
required this.content,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
"Nama Pakaian",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
":",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
nama,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(
|
||||
"Daerah Asal",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
":",
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5),
|
||||
Text(
|
||||
asal,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
Text(
|
||||
content,
|
||||
style: const TextStyle(
|
||||
letterSpacing: 0.2,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class TabMenuButton extends StatelessWidget {
|
||||
height: 20,
|
||||
colorFilter: ColorFilter.mode(
|
||||
isSelected
|
||||
? const Color.fromARGB(255, 106, 24, 207)
|
||||
? const Color.fromARGB(255, 10, 144, 16)
|
||||
: Colors.white,
|
||||
BlendMode.srcIn,
|
||||
),
|
||||
@@ -43,13 +43,16 @@ class TabMenuButton extends StatelessWidget {
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isSelected
|
||||
? const Color.fromARGB(255, 199, 204, 212).withOpacity(0)
|
||||
: Colors.transparent,
|
||||
? const Color.fromARGB(255, 10, 144, 16).withOpacity(0)
|
||||
: const Color.fromARGB(0, 0, 0, 0),
|
||||
|
||||
border:
|
||||
isSelected
|
||||
? const Border(
|
||||
top: BorderSide(color: Colors.white, width: 2),
|
||||
bottom: BorderSide(
|
||||
color: Color.fromARGB(255, 255, 255, 255),
|
||||
width: 2,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
// borderRadius: BorderRadius.circular(20),
|
||||
@@ -63,7 +66,7 @@ class TabMenuButton extends StatelessWidget {
|
||||
color:
|
||||
isSelected
|
||||
? const Color.fromARGB(255, 255, 255, 255)
|
||||
: const Color.fromARGB(255, 0, 0, 0),
|
||||
: const Color.fromARGB(255, 197, 194, 194),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ class TopbarContainer extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _TopbarContainerState extends State<TopbarContainer> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Size size = MediaQuery.of(context).size;
|
||||
return Container(
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'dart:io';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class ImagePickerHelper {
|
||||
Future<dynamic> pickImage() async {
|
||||
final ImagePicker picker = ImagePicker();
|
||||
final XFile? pickedFile = await picker.pickImage(source: ImageSource.gallery);
|
||||
|
||||
if (pickedFile != null) {
|
||||
File imageFile = File(pickedFile.path);
|
||||
return imageFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
class ImagePickerHelper {
|
||||
Future<void> pickImage() async {
|
||||
throw UnsupportedError('pickImage is not supported on this platform');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// import 'package:image_picker_web/image_picker_web.dart';
|
||||
// import 'dart:typed_data';
|
||||
|
||||
// class ImagePickerHelper {
|
||||
// Future<dynamic> pickImage() async {
|
||||
// Uint8List? bytesFromPicker = await ImagePickerWeb.getImageAsBytes();
|
||||
// if (bytesFromPicker != null) {
|
||||
// return bytesFromPicker;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'dart:html' as html;
|
||||
|
||||
class ImagePickerHelper {
|
||||
Future<void> pickImage() async {
|
||||
html.FileUploadInputElement uploadInput = html.FileUploadInputElement();
|
||||
uploadInput.accept = 'image/*';
|
||||
uploadInput.click();
|
||||
|
||||
uploadInput.onChange.listen((event) async {
|
||||
final file = uploadInput.files!.first;
|
||||
final reader = html.FileReader();
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onLoadEnd.listen((event) {
|
||||
Uint8List imageBytes = reader.result as Uint8List;
|
||||
if (imageBytes != null) {
|
||||
return imageBytes;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export 'image_picker_stub.dart'
|
||||
if (dart.library.html) 'image_picker_web.dart'
|
||||
if (dart.library.io) 'image_picker_mobile.dart';
|
||||
@@ -1,15 +1,21 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freekake/components/navbar_container.dart';
|
||||
import 'package:freekake/providers/character_provider.dart';
|
||||
import 'package:freekake/providers/menu_selection_provider.dart';
|
||||
import 'package:freekake/providers/point_provider.dart';
|
||||
import 'package:freekake/screen/Home_screen.dart';
|
||||
import 'package:freekake/screen/drraw_screen.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
void main() {
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
|
||||
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
@@ -18,8 +24,12 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (context) => CharacterProvider(),
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (context) => CharacterProvider()),
|
||||
ChangeNotifierProvider(create: (context) => MenuSelectionProvider()),
|
||||
ChangeNotifierProvider(create: (_) => PointsProvider()),
|
||||
],
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'AR Character App',
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
class Users {
|
||||
int id;
|
||||
// String nama;
|
||||
// String tgl;
|
||||
// String? alamat;
|
||||
|
||||
Users({
|
||||
required this.id,
|
||||
// required this.nama, this.alamat, required this.tgl
|
||||
});
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
"id": id,
|
||||
// "nama": nama, "alamat": this.alamat, "tgl": this.tgl
|
||||
};
|
||||
}
|
||||
|
||||
factory Users.fromJson(Map<String, dynamic> map) {
|
||||
return Users(
|
||||
id: map['id'],
|
||||
// nama: map['nama'],
|
||||
// alamat: map['alamat'],
|
||||
// tgl: map['tanggal'],
|
||||
);
|
||||
}
|
||||
|
||||
String toString() {
|
||||
return 'Users{id:$id,}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MenuSelectionProvider with ChangeNotifier {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
int get selectedIndex => _selectedIndex;
|
||||
|
||||
set selectedIndex(int index) {
|
||||
_selectedIndex = index;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/util/player_point.dart';
|
||||
|
||||
class PointsProvider with ChangeNotifier {
|
||||
PlayerPoints _points = PlayerPoints(xp: 0, gp: 0, sp: 0, level: 1);
|
||||
|
||||
PlayerPoints get points => _points;
|
||||
|
||||
void gainXP(int value) {
|
||||
_points.addXP(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void gainGP(int value) {
|
||||
_points.addGP(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void gainSP(int value) {
|
||||
_points.addSP(value);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -3,335 +3,241 @@ import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/bottom_navbar.dart';
|
||||
import 'package:freekake/components/buildcard_info.dart';
|
||||
import 'package:freekake/components/main_menu.dart';
|
||||
import 'package:freekake/components/menu_item.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/components/topbar_container.dart';
|
||||
import 'package:flutter_unity_widget/flutter_unity_widget.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
HomeScreen({super.key});
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
final String username = "User01";
|
||||
double _sliderValue = 0.0;
|
||||
static const Color transparent = Color(0xFFFFFFFF);
|
||||
final String username = "luffy01";
|
||||
// Image image
|
||||
late UnityWidgetController _unityWidgetController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Size size = MediaQuery.of(context).size;
|
||||
final isLandscape = size.width > size.height;
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final buttonScanSize = screenWidth * 0.20;
|
||||
final double bottomPadding = (9000 / screenWidth).clamp(0, 0.2);
|
||||
double bottomOffset = -(screenWidth * 0.25) + 180;
|
||||
|
||||
return Scaffold(
|
||||
// appBar: AppBar(
|
||||
// elevation: 0,
|
||||
// backgroundColor: Color(0x00000000),
|
||||
// title: Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// children: [
|
||||
// Row(
|
||||
// children: [
|
||||
// Text(
|
||||
// username,
|
||||
// style: const TextStyle(
|
||||
// fontSize: 18,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(width: 10),
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.emoji_events, color: Color(0xFFE32087)),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
// Text(
|
||||
// "5000",
|
||||
// style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// Row(
|
||||
// children: [
|
||||
// IconButton(
|
||||
// icon: Icon(Icons.monetization_on, color: Color(0xFFE32087)),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
// Text(
|
||||
// "1.300",
|
||||
// style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||
// ),
|
||||
// // IconButton(
|
||||
// // icon: Image.asset("images/icon.png", width: 24),
|
||||
// // onPressed: () {},
|
||||
// // ),
|
||||
// ],
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
Positioned.fill(
|
||||
child: RepaintBoundary(
|
||||
// child: Container(
|
||||
// decoration: const BoxDecoration(
|
||||
// image: const DecorationImage(
|
||||
// image: AssetImage("assets/images/background.jpeg"),
|
||||
// fit: BoxFit.cover,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
child: UnityWidget(
|
||||
onUnityCreated: onUnityCreated,
|
||||
)
|
||||
body: homeBody(
|
||||
context,
|
||||
isLandscape,
|
||||
bottomOffset,
|
||||
bottomPadding,
|
||||
screenWidth,
|
||||
buttonScanSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Stack homeBody(
|
||||
BuildContext context,
|
||||
bool isLandscape,
|
||||
double bottomOffset,
|
||||
double bottomPadding,
|
||||
double screenWidth,
|
||||
double buttonScanSize,
|
||||
) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Positioned.fill(
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
decoration: const BoxDecoration(
|
||||
image: DecorationImage(
|
||||
image: AssetImage("assets/images/background.jpeg"),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Positioned(
|
||||
// top: 0,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// child: SizedBox(height: 120, child: TopbarContainer()),
|
||||
// ),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 100,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 20),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
color: Color.fromARGB(1, 209, 174, 174).withAlpha(0),
|
||||
),
|
||||
child: Center(
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2(
|
||||
customButton: Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/images/luffy-u.png'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 100,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 20),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(1),
|
||||
color: Color.fromARGB(1, 209, 174, 174).withAlpha(0),
|
||||
),
|
||||
child: Center(
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton2(
|
||||
customButton: Container(
|
||||
height: 100,
|
||||
width: 100,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/images/cepot-u.png'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
openWithLongPress: false,
|
||||
items: [
|
||||
..._MenuItems.firstItems.map(
|
||||
(item) => DropdownMenuItem<_MenuItem>(
|
||||
value: item,
|
||||
child: _MenuItems.buildItem(item),
|
||||
),
|
||||
),
|
||||
// const DropdownMenuItem<Divider>(
|
||||
// enabled: false,
|
||||
// // height: 8,
|
||||
// child: Divider(),
|
||||
// ),
|
||||
// ..._MenuItems.secondItems.map(
|
||||
// (item) => DropdownMenuItem<_MenuItem>(
|
||||
// value: item,
|
||||
// // height: 48,
|
||||
// child: _MenuItems.buildItem(item),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_MenuItems.onChanged(context, value);
|
||||
} else {
|
||||
print("null");
|
||||
}
|
||||
},
|
||||
buttonStyleData: ButtonStyleData(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
width: 140,
|
||||
padding: EdgeInsets.zero,
|
||||
maxHeight: 150,
|
||||
// padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Color.fromRGBO(2, 104, 17, 1).withAlpha(50),
|
||||
),
|
||||
offset: const Offset(40, -4),
|
||||
),
|
||||
menuItemStyleData: MenuItemStyleData(
|
||||
height: 35,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
),
|
||||
// menuItemStyleData: const MenuItemStyleData(
|
||||
// padding: EdgeInsets.only(left: 16, right: 16),
|
||||
// ),
|
||||
),
|
||||
openWithLongPress: false,
|
||||
items: [
|
||||
..._MenuItems.firstItems.map(
|
||||
(item) => DropdownMenuItem<MenuItem>(
|
||||
value: item,
|
||||
child: _MenuItems.buildItem(item),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
_MenuItems.onChanged(context, value);
|
||||
} else {
|
||||
print("null");
|
||||
}
|
||||
},
|
||||
buttonStyleData: ButtonStyleData(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
width: 140,
|
||||
padding: EdgeInsets.zero,
|
||||
maxHeight: 150,
|
||||
// padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Color.fromRGBO(2, 104, 17, 1).withAlpha(50),
|
||||
),
|
||||
offset: const Offset(40, -4),
|
||||
),
|
||||
menuItemStyleData: MenuItemStyleData(
|
||||
height: 35,
|
||||
padding: EdgeInsets.symmetric(horizontal: 10),
|
||||
),
|
||||
// menuItemStyleData: const MenuItemStyleData(
|
||||
// padding: EdgeInsets.only(left: 16, right: 16),
|
||||
// ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BuildcardInfo(
|
||||
icon: Icons.person,
|
||||
text: username,
|
||||
extraIcon: Icons.emoji_events,
|
||||
extraText: "5000",
|
||||
width: 180,
|
||||
),
|
||||
BuildcardInfo(
|
||||
icon: Icons.monetization_on,
|
||||
text: "1.300",
|
||||
width: 90,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Positioned(
|
||||
// bottom: 150,
|
||||
// left: 0,
|
||||
// right: 0,
|
||||
// child: Column(
|
||||
// children: [
|
||||
// Container(
|
||||
// padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Color.fromARGB(255, 247, 224, 236),
|
||||
// borderRadius: BorderRadius.circular(10),
|
||||
// boxShadow: [
|
||||
// BoxShadow(
|
||||
// color: Color.fromARGB(255, 214, 213, 121),
|
||||
// blurRadius: 5,
|
||||
// spreadRadius: 2,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// child: Text(
|
||||
// "Halo....Jotaslim",
|
||||
// style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Color(0xFF000000),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// SizedBox(height: 10),
|
||||
// setHomeImage("images/luffy.png", 300),
|
||||
// // Image.asset("images/luffy.png", width: 300),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// BG MEnu
|
||||
Stack(
|
||||
),
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 85, child: BottomNavbar()),
|
||||
BuildcardInfo(
|
||||
icon: Icons.person,
|
||||
text: username,
|
||||
extraIcon: Icons.emoji_events,
|
||||
extraText: "5000",
|
||||
width: 180,
|
||||
),
|
||||
BuildcardInfo(
|
||||
icon: Icons.monetization_on,
|
||||
text: "1.300",
|
||||
width: 90,
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(bottom: 0, left: 0, right: 0, child: MainMenu()),
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
left: MediaQuery.of(context).size.width / 2 - 43,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -20),
|
||||
child: ScanButton(),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
bottom: 150,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Color.fromARGB(255, 247, 224, 236),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(255, 214, 213, 121),
|
||||
blurRadius: 5,
|
||||
spreadRadius: 2,
|
||||
),
|
||||
],
|
||||
),
|
||||
// child: Text(
|
||||
// "Halo....Jotaslim",
|
||||
// style: TextStyle(
|
||||
// fontSize: 12,
|
||||
// fontWeight: FontWeight.bold,
|
||||
// color: Color(0xFF000000),
|
||||
// ),
|
||||
// ),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
setHomeImage("assets/images/cepott.png", 300),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// BG MEnu
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 85, child: BottomNavbar()),
|
||||
),
|
||||
],
|
||||
),
|
||||
Positioned(bottom: 0, left: 0, right: 0, child: MainMenu()),
|
||||
|
||||
Positioned(
|
||||
bottom:
|
||||
isLandscape
|
||||
? bottomOffset.clamp(-200.0, -50.0)
|
||||
: bottomPadding * 55,
|
||||
left: (screenWidth - buttonScanSize) / 2,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -30),
|
||||
child: ScanButton(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void onUnityCreated(controller) {
|
||||
_unityWidgetController = controller;
|
||||
}
|
||||
|
||||
void onUnityMessage(message) {
|
||||
print('Received message from unity: ${message.toString()}');
|
||||
}
|
||||
|
||||
// Widget _menuButton(String label, Widget icon) {
|
||||
// return Expanded(
|
||||
// child: Column(
|
||||
// children: [
|
||||
// IconButton(
|
||||
// // icon: Icon(icon, color: Color.fromARGB(255, 179, 161, 170)),
|
||||
// icon: icon,
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
// Text(
|
||||
// label,
|
||||
// style: TextStyle(
|
||||
// color: Color.fromARGB(255, 239, 224, 232),
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// Widget _scanButton() {
|
||||
// return Expanded(
|
||||
// child: Container(
|
||||
// decoration: BoxDecoration(shape: BoxShape.circle, color: transparent),
|
||||
// padding: EdgeInsets.all(10),
|
||||
// child: IconButton(
|
||||
// icon: SvgPicture.asset(
|
||||
// 'icons/Scan.svg',
|
||||
// width: 60,
|
||||
// height: 60,
|
||||
// colorFilter: ColorFilter.mode(Color(0xFFFFFFFF), BlendMode.srcIn),
|
||||
// ),
|
||||
// onPressed: () {},
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
Widget setHomeImage(String src, double size) {
|
||||
return Image.asset("assets/" + src, width: size);
|
||||
return Image.asset(src, width: size);
|
||||
}
|
||||
}
|
||||
|
||||
class _MenuItem {
|
||||
const _MenuItem({required this.text, required this.icon});
|
||||
final String text;
|
||||
final IconData icon;
|
||||
}
|
||||
|
||||
class _MenuItems {
|
||||
static const List<_MenuItem> firstItems = [like, share, download];
|
||||
static const List<_MenuItem> secondItems = [cancel];
|
||||
static const like = _MenuItem(text: 'Like', icon: Icons.favorite);
|
||||
static const share = _MenuItem(text: 'Share', icon: Icons.share);
|
||||
static const download = _MenuItem(text: 'Download', icon: Icons.download);
|
||||
static const cancel = _MenuItem(text: 'Cancel', icon: Icons.cancel);
|
||||
static const List<MenuItem> firstItems = [like, share, download];
|
||||
static const List<MenuItem> secondItems = [cancel];
|
||||
static const like = MenuItem(text: 'Hello', icon: Icons.favorite);
|
||||
static const share = MenuItem(text: 'Furikake', icon: Icons.share);
|
||||
static const download = MenuItem(
|
||||
text: 'Makanan Bergizi',
|
||||
icon: Icons.download,
|
||||
);
|
||||
static const cancel = MenuItem(text: 'Cancel', icon: Icons.cancel);
|
||||
|
||||
static Widget buildItem(_MenuItem item) {
|
||||
static Widget buildItem(MenuItem item) {
|
||||
return Container(
|
||||
height: 30,
|
||||
padding: EdgeInsets.symmetric(vertical: 2),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Row(
|
||||
children: [
|
||||
// Icon(item.icon, color: Color.fromRGBO(242, 240, 240, 1), size: 22),
|
||||
// const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
item.text,
|
||||
@@ -345,7 +251,7 @@ class _MenuItems {
|
||||
);
|
||||
}
|
||||
|
||||
static void onChanged(BuildContext context, _MenuItem item) {
|
||||
static void onChanged(BuildContext context, MenuItem item) {
|
||||
switch (item) {
|
||||
case _MenuItems.like:
|
||||
//Do something
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:camera/camera.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class CameraScreen extends StatefulWidget {
|
||||
const CameraScreen({super.key});
|
||||
|
||||
@override
|
||||
_CameraScreenState createState() => _CameraScreenState();
|
||||
}
|
||||
|
||||
class _CameraScreenState extends State<CameraScreen> {
|
||||
CameraController? controller;
|
||||
List<CameraDescription>? cameras;
|
||||
XFile? capturedImage;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initCamera();
|
||||
}
|
||||
|
||||
Future<void> initCamera() async {
|
||||
await Permission.camera.request();
|
||||
|
||||
if (await Permission.camera.isGranted) {
|
||||
cameras = await availableCameras();
|
||||
controller = CameraController(cameras![0], ResolutionPreset.high);
|
||||
|
||||
await controller!.initialize();
|
||||
setState(() {}); // update UI setelah kamera siap
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (controller == null || !controller!.value.isInitialized) {
|
||||
return Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Camera'),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
CameraPreview(controller!),
|
||||
if (capturedImage != null)
|
||||
Positioned.fill(
|
||||
child: Image.file(File(capturedImage!.path), fit: BoxFit.cover),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FloatingActionButton(
|
||||
backgroundColor: Colors.white,
|
||||
onPressed: () async {
|
||||
final image = await controller!.takePicture();
|
||||
setState(() {
|
||||
capturedImage = image;
|
||||
});
|
||||
|
||||
Navigator.pop(context, capturedImage?.path);
|
||||
},
|
||||
child: Icon(Icons.camera_alt, color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// floatingActionButton: FloatingActionButton(
|
||||
// onPressed: () async {
|
||||
// final image = await controller!.takePicture();
|
||||
// print('Gambar disimpan di: ${image.path}');
|
||||
// // Bisa dilanjutkan untuk upload, crop, dll
|
||||
// },
|
||||
// child: Icon(Icons.camera_alt),
|
||||
// ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/characcter_info.dart';
|
||||
|
||||
class CharacterView extends StatefulWidget {
|
||||
final String title;
|
||||
final String imagePath;
|
||||
final String content;
|
||||
// caracter info
|
||||
final String charName;
|
||||
final String charOrigin;
|
||||
final String charSex;
|
||||
|
||||
const CharacterView({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.imagePath,
|
||||
required this.content,
|
||||
required this.charName,
|
||||
required this.charSex,
|
||||
required this.charOrigin,
|
||||
});
|
||||
|
||||
@override
|
||||
_CharacterViewState createState() => _CharacterViewState();
|
||||
}
|
||||
|
||||
class _CharacterViewState extends State<CharacterView> {
|
||||
late String _content;
|
||||
late String _charName;
|
||||
late String _charOrigin;
|
||||
late String _charSex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_content = widget.content;
|
||||
_charName = widget.charName;
|
||||
_charSex = widget.charSex;
|
||||
_charOrigin = widget.charOrigin;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
backgroundColor: const Color.fromARGB(255, 79, 76, 182),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// Gambar 1/3 dari tinggi layar
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(40),
|
||||
bottomRight: Radius.circular(40),
|
||||
),
|
||||
color: Color.fromARGB(255, 79, 76, 182),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
height: screenHeight / 2,
|
||||
width: double.infinity,
|
||||
child: Image.asset(widget.imagePath, fit: BoxFit.fitHeight),
|
||||
),
|
||||
),
|
||||
// Konten text scrollable
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: CharacterInfo(
|
||||
nama: _charName,
|
||||
kelamin: _charSex,
|
||||
asal: _charOrigin,
|
||||
content: _content,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/collection_container.dart';
|
||||
import 'package:freekake/screen/collection/character_view.dart';
|
||||
|
||||
class CollectionCaraterScreen extends StatefulWidget {
|
||||
const CollectionCaraterScreen({super.key});
|
||||
@@ -22,32 +23,14 @@ class _CollectionCaraterScreenState extends State<CollectionCaraterScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Favorit", style: TextStyle(color: Colors.black)),
|
||||
Divider(height: 20, color: Colors.transparent),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
// alignment: WrapAlignment.spaceAround,
|
||||
spacing: 10, // Horizontal spacing between the containers
|
||||
// runSpacing: 10, // Vertical spacing between lines
|
||||
children: [
|
||||
CollectionContainer(
|
||||
label: "Luffy",
|
||||
imagesrc: 'assets/images/luffy.png',
|
||||
colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
onTapAc: () => {},
|
||||
),
|
||||
CollectionContainer(
|
||||
label: "Oni Chan",
|
||||
imagesrc: 'assets/images/klipartz.png',
|
||||
onTapAc: () => {},
|
||||
),
|
||||
],
|
||||
Text(
|
||||
"Favorite",
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Divider(height: 20, color: Colors.transparent),
|
||||
Text("Semua", style: TextStyle(color: Colors.black)),
|
||||
Divider(height: 20, color: Colors.transparent),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
@@ -55,22 +38,112 @@ class _CollectionCaraterScreenState extends State<CollectionCaraterScreen> {
|
||||
spacing: 10, // Horizontal spacing between the containers
|
||||
// runSpacing: 10, // Vertical spacing between lines
|
||||
children: [
|
||||
CollectionContainer(
|
||||
label: "Luffy",
|
||||
imagesrc: 'assets/images/luffy.png',
|
||||
colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
onTapAc: () => {},
|
||||
),
|
||||
CollectionContainer(
|
||||
label: "Oni Chan",
|
||||
imagesrc: 'assets/images/klipartz.png',
|
||||
onTapAc: () => {},
|
||||
),
|
||||
CollectionContainer(
|
||||
label: "Cepot",
|
||||
imagesrc: 'assets/images/cepott.png',
|
||||
colorContiner: Color.fromRGBO(237, 207, 100, 1.0),
|
||||
onTapAc: () => {},
|
||||
onTapAc:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(context) => CharacterView(
|
||||
title: "Tentang Cepot",
|
||||
imagePath: 'assets/images/cepott.png',
|
||||
charName: "Cepot",
|
||||
charSex: "Laki-laki",
|
||||
charOrigin: "Jawa Barat",
|
||||
content:
|
||||
"""Cepot adalah sosok yang begitu menarik—aneh, ceria, dan penuh kejutan!
|
||||
Ia muncul seperti tokoh dari dongeng lama yang lupa pulang ke rumah, dengan wajah merah seperti apel matang,
|
||||
hidung besar yang tampak siap mencium rahasia, dan mata bulat yang selalu bersinar penuh rasa ingin tahu. Ia suka tertawa keras,
|
||||
suka bermain-main dengan kata-kata, dan sangat pandai membuat siapa pun merasa nyaman di dekatnya. \nMeski kadang terlihat konyol,
|
||||
Cepot sebenarnya sangat cerdik—ia tahu kapan harus berbicara, kapan harus diam, dan yang paling penting, ia tahu bagaimana menghibur
|
||||
orang-orang yang sedang sedih. \nAnak-anak menyukainya, orang tua menghormatinya, dan tak jarang hewan-hewan kecil pun tampak betah
|
||||
duduk di dekatnya saat ia bercerita.
|
||||
""",
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
// CollectionContainer(
|
||||
// label: "Luffy",
|
||||
// imagesrc: 'assets/images/luffy.png',
|
||||
// colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
// CollectionContainer(
|
||||
// label: "Oni Chan",
|
||||
// imagesrc: 'assets/images/klipartz.png',
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(height: 20, color: Colors.transparent),
|
||||
Text(
|
||||
"All",
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Divider(height: 20, color: Colors.transparent),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
// alignment: WrapAlignment.spaceAround,
|
||||
spacing: 10, // Horizontal spacing between the containers
|
||||
// runSpacing: 10, // Vertical spacing between lines
|
||||
children: [
|
||||
// CollectionContainer(
|
||||
// label: "Luffy",
|
||||
// imagesrc: 'assets/images/luffy.png',
|
||||
// colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
// CollectionContainer(
|
||||
// label: "Oni Chan",
|
||||
// imagesrc: 'assets/images/klipartz.png',
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
CollectionContainer(
|
||||
label: "Cepot",
|
||||
imagesrc: 'assets/images/cepott.png',
|
||||
colorContiner: Color.fromRGBO(237, 207, 100, 1.0),
|
||||
onTapAc:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(context) => CharacterView(
|
||||
title: "Tentang Cepot",
|
||||
imagePath: 'assets/images/cepott.png',
|
||||
charName: "Cepot",
|
||||
charSex: "Laki-laki",
|
||||
charOrigin: "Jawa Barat",
|
||||
content: """
|
||||
Cepot adalah sosok yang begitu menarik—aneh,
|
||||
ceria, dan penuh kejutan! Ia muncul seperti
|
||||
tokoh dari dongeng lama yang lupa pulang ke rumah,
|
||||
dengan wajah merah seperti apel matang,
|
||||
hidung besar yang tampak siap
|
||||
mencium rahasia, dan mata bulat
|
||||
yang selalu bersinar penuh rasa ingin
|
||||
tahu. Ia suka tertawa keras, suka bermain-main dengan kata-kata, dan sangat
|
||||
pandai membuat siapa pun merasa nyaman di dekatnya. \nMeski kadang terlihat konyol,
|
||||
Cepot sebenarnya sangat cerdik—ia tahu kapan harus berbicara, kapan harus diam, dan
|
||||
yang paling penting, ia tahu bagaimana menghibur orang-orang yang sedang sedih. \nAnak-anak
|
||||
menyukainya, orang tua menghormatinya, dan tak jarang hewan-hewan kecil pun tampak betah duduk
|
||||
di dekatnya saat ia bercerita.
|
||||
""",
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -27,14 +27,14 @@ class _CollectionFragmentScreenState extends State<CollectionFragmentScreen> {
|
||||
spacing: 10, // Horizontal spacing between the containers
|
||||
runSpacing: 10, // Vertical spacing between lines
|
||||
children: [
|
||||
// CollectionContainer(
|
||||
// label: "Luffy",
|
||||
// imagesrc: 'assets/images/ft_luffy.png',
|
||||
// onTapAc: () => {},
|
||||
// colorContiner: ColorHelper.hexToColor("0xE2E5E9"),
|
||||
// ),
|
||||
CollectionContainer(
|
||||
label: "Luffy",
|
||||
imagesrc: 'assets/images/ft_luffy.png',
|
||||
onTapAc: () => {},
|
||||
colorContiner: ColorHelper.hexToColor("0xE2E5E9"),
|
||||
),
|
||||
CollectionContainer(
|
||||
label: "Oni Chan",
|
||||
label: "Cepot",
|
||||
imagesrc: 'assets/images/ft_cepot.png',
|
||||
onTapAc: () => {},
|
||||
colorContiner: ColorHelper.hexToColor("0xE2E5E9"),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/collection_container.dart';
|
||||
import 'package:freekake/screen/collection/character_view.dart';
|
||||
import 'package:freekake/screen/collection/skin_view.dart';
|
||||
|
||||
class CollectionSkinScreen extends StatefulWidget {
|
||||
const CollectionSkinScreen({super.key});
|
||||
@@ -11,7 +13,7 @@ class CollectionSkinScreen extends StatefulWidget {
|
||||
class _CollectionSkinScreenState extends State<CollectionSkinScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
return SizedBox(
|
||||
// scrollDirection: Axis.horizontal,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Column(
|
||||
@@ -23,53 +25,70 @@ class _CollectionSkinScreenState extends State<CollectionSkinScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("Luffy", style: TextStyle(color: Colors.black)),
|
||||
Divider(height: 10),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
spacing: 10, // Horizontal spacing between the containers
|
||||
children: [
|
||||
CollectionContainer(
|
||||
label: "Luffy Kimono",
|
||||
imagesrc: 'assets/images/luffy-kimono.png',
|
||||
colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
onTapAc: () => {},
|
||||
),
|
||||
// Text("Luffy", style: TextStyle(color: Colors.black)),
|
||||
// Divider(height: 10),
|
||||
// SingleChildScrollView(
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: Row(
|
||||
// spacing: 10, // Horizontal spacing between the containers
|
||||
// children: [
|
||||
// CollectionContainer(
|
||||
// label: "Luffy Kimono",
|
||||
// imagesrc: 'assets/images/luffy-kimono.png',
|
||||
// colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
|
||||
CollectionContainer(
|
||||
label: "Luffy (Default)",
|
||||
imagesrc: 'assets/images/luffy.png',
|
||||
colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
onTapAc: () => {},
|
||||
),
|
||||
CollectionContainer(
|
||||
label: "Luffy King",
|
||||
imagesrc: 'assets/images/luffy-king.png',
|
||||
colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
onTapAc: () => {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(height: 20, color: Colors.transparent),
|
||||
Text("Lainnya", style: TextStyle(color: Colors.black)),
|
||||
// CollectionContainer(
|
||||
// label: "Luffy (Default)",
|
||||
// imagesrc: 'assets/images/luffy.png',
|
||||
// colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
// CollectionContainer(
|
||||
// label: "Luffy King",
|
||||
// imagesrc: 'assets/images/luffy-king.png',
|
||||
// colorContiner: Color.fromRGBO(12, 199, 215, 1),
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// Divider(height: 20, color: Colors.transparent),
|
||||
Text("Cepot", style: TextStyle(color: Colors.black)),
|
||||
Divider(height: 10),
|
||||
SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
CollectionContainer(
|
||||
label: "Oni Chan (Default)",
|
||||
imagesrc: 'assets/images/klipartz.png',
|
||||
onTapAc: () => {},
|
||||
),
|
||||
// CollectionContainer(
|
||||
// label: "Oni Chan (Default)",
|
||||
// imagesrc: 'assets/images/klipartz.png',
|
||||
// onTapAc: () => {},
|
||||
// ),
|
||||
CollectionContainer(
|
||||
label: "Cepot (Default)",
|
||||
imagesrc: 'assets/images/cepott.png',
|
||||
colorContiner: Color.fromRGBO(237, 207, 100, 1.0),
|
||||
onTapAc: () => {},
|
||||
onTapAc:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(context) => SkinView(
|
||||
title: "Tentang Skin",
|
||||
imagePath: 'assets/images/cepott.png',
|
||||
skinName: "Pangsi",
|
||||
skinOrigin: "Jawa Barat",
|
||||
content:
|
||||
"""Pakaian Cepot juga punya makna, lho! Ia selalu memakai baju pangsi hitam panjang yang sederhana, melambangkan kesederhanaan dan kerendahan hati. \nBaju ini longgar dan nyaman, cocok untuk Cepot yang suka bergerak bebas dan menghibur orang-orang di sekitarnya. \nSarung kotak-kotak yang dililit di pinggang bukan hanya pelengkap, tapi juga simbol budaya rakyat biasa yang tetap rapi dan terhormat. \nDan jangan lupa iket batik di kepalanya—itu bukan cuma hiasan, tapi lambang kebanggaan dan identitas sebagai orang Sunda. Lewat pakaiannya, Cepot mengajarkan kita bahwa kita tidak perlu pakaian mahal untuk terlihat hebat. Yang penting adalah sikap baik, rasa hormat pada budaya, dan keberanian untuk jadi diri sendiri!
|
||||
""",
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/characcter_info.dart';
|
||||
import 'package:freekake/components/skin_info.dart';
|
||||
|
||||
class SkinView extends StatefulWidget {
|
||||
final String title;
|
||||
final String imagePath;
|
||||
final String content;
|
||||
// caracter info
|
||||
final String skinName;
|
||||
final String skinOrigin;
|
||||
|
||||
const SkinView({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.imagePath,
|
||||
required this.content,
|
||||
required this.skinName,
|
||||
required this.skinOrigin,
|
||||
});
|
||||
|
||||
@override
|
||||
_SkinViewState createState() => _SkinViewState();
|
||||
}
|
||||
|
||||
class _SkinViewState extends State<SkinView> {
|
||||
late String _content;
|
||||
late String _skinName;
|
||||
late String _skinOrigin;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_content = widget.content;
|
||||
_skinName = widget.skinName;
|
||||
_skinOrigin = widget.skinOrigin;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenHeight = MediaQuery.of(context).size.height;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
backgroundColor: const Color.fromARGB(255, 79, 76, 182),
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(40),
|
||||
bottomRight: Radius.circular(40),
|
||||
),
|
||||
color: Color.fromARGB(255, 79, 76, 182),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
height: screenHeight / 2,
|
||||
width: double.infinity,
|
||||
child: Image.asset(widget.imagePath, fit: BoxFit.fitHeight),
|
||||
),
|
||||
),
|
||||
// Konten text scrollable
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: SkinInfo(
|
||||
nama: _skinName,
|
||||
asal: _skinOrigin,
|
||||
content: _content,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/bottom_navbar.dart';
|
||||
import 'package:freekake/components/buildcard_info.dart';
|
||||
import 'package:freekake/components/curve_bottom_border_tab.dart';
|
||||
import 'package:freekake/components/main_menu.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/components/tab_menu.dart';
|
||||
@@ -35,129 +36,140 @@ class _KoleksiScreenState extends State<KoleksiScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final buttonScanSize = screenWidth * 0.20;
|
||||
final double bottomPadding = (9000 / screenWidth).clamp(0, 0.2);
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
body: Stack(
|
||||
children: <Widget>[
|
||||
Positioned.fill(
|
||||
child: Container(color: const Color.fromARGB(255, 255, 255, 255)),
|
||||
),
|
||||
Positioned.fill(
|
||||
top: 30,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
},
|
||||
children: [
|
||||
const CollectionCaraterScreen(),
|
||||
const CollectionSkinScreen(),
|
||||
const CollectionFragmentScreen(),
|
||||
],
|
||||
),
|
||||
),
|
||||
// BG MEnu
|
||||
Stack(
|
||||
body: koleksiBody(bottomPadding, screenWidth, buttonScanSize),
|
||||
);
|
||||
}
|
||||
|
||||
Stack koleksiBody(
|
||||
double bottomPadding,
|
||||
double screenWidth,
|
||||
double buttonScanSize,
|
||||
) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
Positioned.fill(
|
||||
child: Container(color: const Color.fromARGB(255, 255, 255, 255)),
|
||||
),
|
||||
Positioned.fill(
|
||||
top: 30,
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
onPageChanged: (index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
},
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 85, child: BottomNavbar()),
|
||||
const CollectionCaraterScreen(),
|
||||
const CollectionSkinScreen(),
|
||||
const CollectionFragmentScreen(),
|
||||
],
|
||||
),
|
||||
),
|
||||
// BG MEnu
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 85, child: BottomNavbar()),
|
||||
),
|
||||
],
|
||||
),
|
||||
//TopBar
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 130, child: TopbarContainer()),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BuildcardInfo(
|
||||
icon: Icons.person,
|
||||
text: 'User01',
|
||||
extraIcon: Icons.emoji_events,
|
||||
extraText: "5000",
|
||||
width: 180,
|
||||
),
|
||||
BuildcardInfo(
|
||||
icon: Icons.monetization_on,
|
||||
text: "1.300",
|
||||
width: 90,
|
||||
),
|
||||
],
|
||||
),
|
||||
//TopBar
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 130, child: TopbarContainer()),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 10,
|
||||
left: 10,
|
||||
right: 10,
|
||||
),
|
||||
// Positioned.fill(
|
||||
// top: 30,
|
||||
// child: PageView(
|
||||
// controller: _pageController,
|
||||
// onPageChanged: (index) {
|
||||
// setState(() {
|
||||
// _selectedIndex = index;
|
||||
// });
|
||||
// },
|
||||
// children: [
|
||||
// const CollectionCaraterScreen(),
|
||||
// const CollectionSkinScreen(),
|
||||
// const CollectionFragmentScreen(),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
Positioned(
|
||||
top: 70,
|
||||
left: 10,
|
||||
right: 10,
|
||||
child: Container(
|
||||
// decoration: const BoxDecoration(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
// color: Color.fromARGB(116, 125, 129, 133),
|
||||
// ),
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
BuildcardInfo(
|
||||
icon: Icons.person,
|
||||
text: 'User01',
|
||||
extraIcon: Icons.emoji_events,
|
||||
extraText: "5000",
|
||||
width: 180,
|
||||
CurvedBottomBorderTab(
|
||||
label: "Karakter",
|
||||
isSelected: _selectedIndex == 0,
|
||||
tap: () => _onMenuTapped(0),
|
||||
),
|
||||
BuildcardInfo(
|
||||
icon: Icons.monetization_on,
|
||||
text: "1.300",
|
||||
width: 90,
|
||||
CurvedBottomBorderTab(
|
||||
label: "Skin",
|
||||
isSelected: _selectedIndex == 1,
|
||||
tap: () => _onMenuTapped(1),
|
||||
),
|
||||
CurvedBottomBorderTab(
|
||||
label: "Fragment",
|
||||
isSelected: _selectedIndex == 2,
|
||||
tap: () => _onMenuTapped(2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Positioned.fill(
|
||||
// top: 30,
|
||||
// child: PageView(
|
||||
// controller: _pageController,
|
||||
// onPageChanged: (index) {
|
||||
// setState(() {
|
||||
// _selectedIndex = index;
|
||||
// });
|
||||
// },
|
||||
// children: [
|
||||
// const CollectionCaraterScreen(),
|
||||
// const CollectionSkinScreen(),
|
||||
// const CollectionFragmentScreen(),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
Positioned(
|
||||
top: 70,
|
||||
left: 10,
|
||||
right: 10,
|
||||
child: Container(
|
||||
// decoration: const BoxDecoration(
|
||||
// borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
// color: Color.fromARGB(116, 125, 129, 133),
|
||||
// ),
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
TabMenuButton(
|
||||
label: "Karakter",
|
||||
isSelected: _selectedIndex == 0,
|
||||
onPress: () => _onMenuTapped(0),
|
||||
),
|
||||
TabMenuButton(
|
||||
label: "Skin",
|
||||
isSelected: _selectedIndex == 1,
|
||||
onPress: () => _onMenuTapped(1),
|
||||
),
|
||||
TabMenuButton(
|
||||
label: "Fragment",
|
||||
isSelected: _selectedIndex == 2,
|
||||
onPress: () => _onMenuTapped(2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(bottom: 0, left: 0, right: 0, child: MainMenu()),
|
||||
Positioned(
|
||||
bottom: bottomPadding * 98,
|
||||
left: (screenWidth - buttonScanSize) / 2,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -20),
|
||||
child: ScanButton(),
|
||||
),
|
||||
Positioned(bottom: 0, left: 0, right: 0, child: MainMenu()),
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
left: MediaQuery.of(context).size.width / 2 - 43,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -20),
|
||||
child: ScanButton(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
class _ListDetailScreen extends StatefulWidget {
|
||||
final String title;
|
||||
final String? filepath;
|
||||
|
||||
const _ListDetailScreen({super.key, required this.title, this.filepath});
|
||||
|
||||
@override
|
||||
State<_ListDetailScreen> createState() => _ListDetailScreenState();
|
||||
}
|
||||
|
||||
class _ListDetailScreenState extends State<_ListDetailScreen> {
|
||||
List<bool> checklist = [];
|
||||
late WebViewController _controller;
|
||||
late String? fPath = widget.filepath;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller =
|
||||
WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted);
|
||||
checklist = List.generate(paragraphs.length, (index) => false);
|
||||
if (fPath != null && fPath!.isNotEmpty) {
|
||||
_loadContent(fPath!); // <- ini penting
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> paragraphs = [
|
||||
"Paragraf pertama tentang ${"widget.title"}.",
|
||||
"Paragraf kedua dengan informasi tambahan.",
|
||||
"Paragraf ketiga yang menjelaskan detail lebih lanjut.",
|
||||
"Paragraf keempat yang melengkapi pembahasan.",
|
||||
];
|
||||
|
||||
void _loadContent(String path) async {
|
||||
try {
|
||||
if (path.startsWith('http')) {
|
||||
_controller.loadRequest(Uri.parse(path));
|
||||
} else {
|
||||
String fileHtml = await rootBundle.loadString(path);
|
||||
_controller.loadHtmlString(fileHtml);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Error loading HTML: $e");
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(title: Text(widget.title)),
|
||||
body:
|
||||
(fPath != null && fPath!.isNotEmpty)
|
||||
? WebViewWidget(controller: _controller)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/default.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Ini adalah detail dari ${widget.title}.",
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: paragraphs.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Text(paragraphs[index]),
|
||||
trailing: Checkbox(
|
||||
value: checklist[index],
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
checklist[index] = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +1,133 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class ListDetailScreen extends StatefulWidget {
|
||||
final String title;
|
||||
final String? filepath;
|
||||
|
||||
const ListDetailScreen({super.key, required this.title});
|
||||
const ListDetailScreen({super.key, required this.title, this.filepath});
|
||||
|
||||
@override
|
||||
State<ListDetailScreen> createState() => _ListDetailScreenState();
|
||||
}
|
||||
|
||||
class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
List<bool> checklist = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
checklist = List.generate(paragraphs.length, (index) => false);
|
||||
}
|
||||
|
||||
final List<String> paragraphs = [
|
||||
"Paragraf pertama tentang ${"widget.title"}.",
|
||||
"Paragraf kedua dengan informasi tambahan.",
|
||||
"Paragraf ketiga yang menjelaskan detail lebih lanjut.",
|
||||
"Paragraf keempat yang melengkapi pembahasan.",
|
||||
];
|
||||
late InAppWebViewController _webViewController;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String _getMimeType(String path) {
|
||||
if (path.endsWith('.png')) return 'image/png';
|
||||
if (path.endsWith('.jpg') || path.endsWith('.jpeg')) return 'image/jpeg';
|
||||
if (path.endsWith('.svg')) return 'image/svg+xml';
|
||||
if (path.endsWith('.html')) return 'text/html';
|
||||
if (path.endsWith('.css')) return 'text/css';
|
||||
if (path.endsWith('.js')) return 'application/javascript';
|
||||
return 'application/octet-stream';
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(title: Text(widget.title)),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Gambar di kiri atas dengan teks di kanan
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/images/default.png', // Ganti dengan gambar yang sesuai
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Ini adalah detail dari ${widget.title}.",
|
||||
style: const TextStyle(fontSize: 18),
|
||||
body:
|
||||
widget.filepath != null
|
||||
? InAppWebView(
|
||||
initialUrlRequest: URLRequest(url: WebUri("about:blank")),
|
||||
initialOptions: InAppWebViewGroupOptions(
|
||||
crossPlatform: InAppWebViewOptions(
|
||||
javaScriptEnabled: true,
|
||||
allowFileAccessFromFileURLs: true,
|
||||
allowUniversalAccessFromFileURLs: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
onWebViewCreated: (controller) async {
|
||||
_webViewController = controller;
|
||||
|
||||
// Paragraf dengan checklist
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: paragraphs.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Text(paragraphs[index]),
|
||||
trailing: Checkbox(
|
||||
value: checklist[index],
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
checklist[index] = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
final html = await rootBundle.loadString(widget.filepath!);
|
||||
final base = widget.filepath!.substring(
|
||||
0,
|
||||
widget.filepath!.lastIndexOf('/') + 1,
|
||||
);
|
||||
await controller.loadData(
|
||||
data: html,
|
||||
baseUrl: WebUri(
|
||||
"file:///android_asset/flutter_assets/$base",
|
||||
), // agar path relatif seperti img1/* jalan
|
||||
mimeType: 'text/html',
|
||||
encoding: 'utf-8',
|
||||
);
|
||||
debugPrint("http://localhost/$base");
|
||||
debugPrint("file:///android_asset/flutter_assets/$base");
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
onLoadStop: (controller, url) async {
|
||||
debugPrint("🚀 onLoadStop called. URL: $url");
|
||||
|
||||
var result = await controller.evaluateJavascript(
|
||||
source: """
|
||||
(() => {
|
||||
const board = document.getElementById('game-board');
|
||||
const hasRender = typeof renderGrid === 'function';
|
||||
return { hasRender, boardExists: board !== null };
|
||||
})();
|
||||
""",
|
||||
);
|
||||
|
||||
debugPrint("📊 Evaluated JS result: $result");
|
||||
|
||||
final jsCheck = await controller.evaluateJavascript(
|
||||
source: """
|
||||
typeof renderGrid === 'function' &&
|
||||
document.querySelector('#game-board') !== null
|
||||
""",
|
||||
);
|
||||
|
||||
debugPrint(
|
||||
"Apakah renderGrid tersedia dan board muncul? => $jsCheck",
|
||||
);
|
||||
|
||||
final imageLoaded = await controller.evaluateJavascript(
|
||||
source: """
|
||||
Array.from(document.images).every(img => img.complete)
|
||||
""",
|
||||
);
|
||||
|
||||
debugPrint("Semua gambar berhasil dimuat? => $imageLoaded");
|
||||
},
|
||||
|
||||
// (Opsional) tangkap log dari JS
|
||||
onConsoleMessage: (controller, consoleMessage) {
|
||||
debugPrint("📢 JS Console: ${consoleMessage.message}");
|
||||
},
|
||||
shouldInterceptRequest: (controller, request) async {
|
||||
final url = request.url;
|
||||
if (url == null || url.host != 'localhost') return null;
|
||||
|
||||
try {
|
||||
// Strip "http://localhost/" from the path
|
||||
final path = url.path.replaceFirst('/', '');
|
||||
|
||||
final asset = await rootBundle.load(path);
|
||||
final mimeType = _getMimeType(path);
|
||||
|
||||
return WebResourceResponse(
|
||||
contentType: mimeType,
|
||||
data: asset.buffer.asUint8List(),
|
||||
statusCode: 200,
|
||||
reasonPhrase: 'OK',
|
||||
);
|
||||
} catch (e) {
|
||||
print("⚠️ Asset not found: ${url.path} -> $e");
|
||||
return WebResourceResponse(
|
||||
contentType: 'text/plain',
|
||||
data: Uint8List.fromList(utf8.encode('404 Not Found')),
|
||||
statusCode: 404,
|
||||
reasonPhrase: 'Not Found',
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const Center(child: Text("Tidak ada file HTML")),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/buildcard_info.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:freekake/components/card_list.dart';
|
||||
import 'package:freekake/components/collection_container%20copy.dart';
|
||||
import 'package:freekake/components/main_menu.dart';
|
||||
import 'package:freekake/components/topbar_container.dart';
|
||||
import 'package:freekake/screen/pustaka/pustaka_detail_screen.dart';
|
||||
import 'package:freekake/helpers/color_helper.dart';
|
||||
import 'package:freekake/models/users.dart';
|
||||
import 'package:freekake/screen/pustaka/list_detail_screen.dart';
|
||||
import 'package:freekake/util/api_client.dart';
|
||||
import 'package:list_detail_extension/list_detail_extension.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class ListEducation extends StatefulWidget {
|
||||
const ListEducation({super.key});
|
||||
final String? selectedCategory;
|
||||
const ListEducation({super.key, this.selectedCategory});
|
||||
|
||||
@override
|
||||
State<ListEducation> createState() => _ListEducationState();
|
||||
@@ -16,6 +24,22 @@ class ListEducation extends StatefulWidget {
|
||||
|
||||
class _ListEducationState extends State<ListEducation> {
|
||||
TextEditingController searchController = TextEditingController();
|
||||
late final WebViewController _controller;
|
||||
late String urlOrAssetPath;
|
||||
bool _shouldShowWebView = false;
|
||||
String? selectedCategory;
|
||||
final listDetailExtension = ListDetailExtension();
|
||||
|
||||
// Data fetch
|
||||
final ApiClient _apiClient = ApiClient();
|
||||
List<Users> _posts = [];
|
||||
bool _isLoading = true;
|
||||
String _error = '';
|
||||
|
||||
Future<void> _initExtension() async {
|
||||
await listDetailExtension.load();
|
||||
}
|
||||
|
||||
List<String> allItems = [
|
||||
"Matematika",
|
||||
"Fisika",
|
||||
@@ -24,27 +48,170 @@ class _ListEducationState extends State<ListEducation> {
|
||||
"Sejarah",
|
||||
"Geografi",
|
||||
];
|
||||
List<String> filteredItems = [];
|
||||
|
||||
final List<Map<String, dynamic>> _listContent = [
|
||||
{
|
||||
"title": "Taburan Lezat untuk Nasi!",
|
||||
"image": "assets/html/furikake/furikake.jpg",
|
||||
"color": "#cdd0ee",
|
||||
"category": "Kesehatan",
|
||||
"type": "Materi",
|
||||
"point": "10",
|
||||
"coint": "100",
|
||||
"body":
|
||||
"""Tahukah kamu bahwa di Jepang ada bumbu tabur yang bisa membuat nasi
|
||||
menjadi lebih enak?
|
||||
""",
|
||||
"file": "assets/html/furikake/index.html",
|
||||
},
|
||||
{
|
||||
"title": "Makan Sehat dengan Gizi Seimbang",
|
||||
"image": "assets/html/lemak.png",
|
||||
"color": "#cef1da",
|
||||
"category": "Keselamatan",
|
||||
"type": "Materi",
|
||||
"point": "100",
|
||||
"coint": "20",
|
||||
"body":
|
||||
"""upaya makan kita sehat, ada aturan "Isi Piringku" yang bisa kita ikuti:
|
||||
🍽️ Separuh piring: Sayur dan buah 🍽️ Seperempat piring: Karbohidrat (seperti nasi atau roti)
|
||||
🍽️ Seperempat piring: Protein (seperti ayam atau tempe) 🍽️ Sedikit lemak sehat
|
||||
""",
|
||||
"file": "assets/html/index.html",
|
||||
},
|
||||
{
|
||||
"title": "Memori Game",
|
||||
"image": "assets/html/freekake.png",
|
||||
"color": "#cef1da",
|
||||
"category": "Gizi",
|
||||
"type": "Materi",
|
||||
"point": "100",
|
||||
"coint": "20",
|
||||
"body":
|
||||
"""Game untuk melatih daya ingat siswa, sehingga bisa diharapkan bisa
|
||||
menemukan cara sendiri untuk mengingat sesuatu
|
||||
""",
|
||||
"file": "assets/html/memory/memory.html",
|
||||
},
|
||||
{
|
||||
"title": "Find Words",
|
||||
"image": "assets/html/freekake.png",
|
||||
"color": "#cef1da",
|
||||
"category": "Kesehatan",
|
||||
"type": "Materi",
|
||||
"point": "100",
|
||||
"coint": "20",
|
||||
"body": """
|
||||
No Descripptions
|
||||
""",
|
||||
"file": "assets/html/findwords/findwords.html",
|
||||
},
|
||||
];
|
||||
List<Map<String, dynamic>> filteredItems = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
filteredItems = List.from(allItems);
|
||||
|
||||
selectedCategory = widget.selectedCategory;
|
||||
print(selectedCategory);
|
||||
if (selectedCategory != null && selectedCategory!.isNotEmpty) {
|
||||
filteredItems =
|
||||
_listContent
|
||||
.where((item) => item["category"] == selectedCategory)
|
||||
.toList();
|
||||
} else {
|
||||
filteredItems = List.from(_listContent);
|
||||
}
|
||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||
_shouldShowWebView = true;
|
||||
_controller =
|
||||
WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted);
|
||||
urlOrAssetPath = "assets/html/index.html";
|
||||
_loadContent(urlOrAssetPath);
|
||||
_initExtension();
|
||||
}
|
||||
}
|
||||
|
||||
void _loadContent(String path) async {
|
||||
if (_shouldShowWebView) {
|
||||
if (path.startsWith('http')) {
|
||||
// Load dari Internet
|
||||
_controller.loadRequest(Uri.parse(path));
|
||||
} else {
|
||||
try {
|
||||
await rootBundle.load(path);
|
||||
await _controller.loadFlutterAsset(path);
|
||||
} catch (e) {
|
||||
print("Gagal memuat file HTML: $path\nError: $e");
|
||||
}
|
||||
_controller.loadFlutterAsset(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void filterSearch(String query) {
|
||||
setState(() {
|
||||
filteredItems =
|
||||
allItems
|
||||
.where((item) => item.toLowerCase().contains(query.toLowerCase()))
|
||||
.toList();
|
||||
selectedCategory = null;
|
||||
if (query.isEmpty) {
|
||||
filteredItems = List.from(_listContent);
|
||||
} else {
|
||||
filteredItems =
|
||||
_listContent.where((item) {
|
||||
return item["title"].toLowerCase().contains(query.toLowerCase());
|
||||
}).toList();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void filterByCategory(String category) {
|
||||
setState(() {
|
||||
selectedCategory = category;
|
||||
filteredItems =
|
||||
_listContent.where((item) {
|
||||
return item["category"].toLowerCase() == category.toLowerCase();
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _fetchPosts() async {
|
||||
try {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = '';
|
||||
});
|
||||
|
||||
// Melakukan permintaan GET ke endpoint
|
||||
final Response response = await _apiClient.dio.get('auth/users');
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
// Konversi data JSON ke List
|
||||
List<dynamic> postJson = response.data;
|
||||
_posts = postJson.map((json) => Users.fromJson(json)).toList();
|
||||
} else {
|
||||
_error = 'Gagal memuat data: ${response.statusCode}';
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
if (e.response != null) {
|
||||
_error =
|
||||
'Server Error: ${e.response?.statusCode} - ${e.response?.statusMessage}';
|
||||
} else {
|
||||
_error = 'Network Error: ${e.message}';
|
||||
}
|
||||
print('DioError: $e');
|
||||
} catch (e) {
|
||||
_error = 'Terjadi kesalahan tidak terduga: $e';
|
||||
print('General Error: $e');
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String _searchQuery = "";
|
||||
final List<Map<String, dynamic>> _collections = [
|
||||
final List<Map<String, dynamic>> collections = [
|
||||
{
|
||||
"label": "Kesehatan",
|
||||
"image": "assets/icons/healthy.svg",
|
||||
@@ -70,286 +237,249 @@ class _ListEducationState extends State<ListEducation> {
|
||||
"category": "keselamatan",
|
||||
},
|
||||
];
|
||||
List<Map<String, dynamic>> filteredCollections =
|
||||
_collections
|
||||
|
||||
final List<Map<String, dynamic>> filteredCollections =
|
||||
collections
|
||||
.where(
|
||||
(item) => item["label"]!.toLowerCase().contains(
|
||||
_searchQuery.toLowerCase(),
|
||||
(item) => item["label"].toLowerCase().contains(
|
||||
// searchQuery.toLowerCase(),
|
||||
searchController.text.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
actions: <Widget>[],
|
||||
),
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Column(
|
||||
children: [
|
||||
// Header Section
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(225, 79, 76, 182),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(25),
|
||||
bottomRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 6,
|
||||
),
|
||||
child: Container(
|
||||
height: 47,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.search,
|
||||
color: Color.fromARGB(137, 180, 172, 172),
|
||||
size: 18,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged:
|
||||
filterSearch, // Memanggil filter saat mengetik
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 14,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Cari pelajaran...',
|
||||
hintStyle: TextStyle(color: Colors.black54),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (searchController.text.isNotEmpty)
|
||||
IconButton(
|
||||
icon: const Icon(
|
||||
Icons.close,
|
||||
color: Colors.black54,
|
||||
),
|
||||
onPressed: () {
|
||||
searchController.clear();
|
||||
filterSearch('');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children:
|
||||
filteredCollections.map((item) {
|
||||
return CollectionContainer(
|
||||
label: item["label"]!,
|
||||
imageSvg: item["image"]!,
|
||||
iconw: 20,
|
||||
iconh: 20,
|
||||
width: 70,
|
||||
height: 70,
|
||||
textColor: Colors.black,
|
||||
lblSize: 10,
|
||||
colorContiner: ColorHelper.hexToColor(item["color"]),
|
||||
onTapAc:
|
||||
() => {
|
||||
searchController.clear(),
|
||||
filterSearch(item["category"]),
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_shouldShowWebView) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
leading: IconButton(
|
||||
icon: Icon(Icons.arrow_back),
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
actions: <Widget>[],
|
||||
),
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Column(
|
||||
children: [
|
||||
// Header Section
|
||||
headerContainer(context, collections),
|
||||
// Collection Items dengan Filter
|
||||
SizedBox(height: 20),
|
||||
Expanded(child: contentContainer(context)),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(40),
|
||||
child: Text("WebView tidak tersedia untuk platform ini."),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Search Bar
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||
// child: Container(
|
||||
// height: 47,
|
||||
// decoration: BoxDecoration(
|
||||
// borderRadius: BorderRadius.circular(20),
|
||||
// color: Colors.grey.shade200,
|
||||
// ),
|
||||
// padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
// child: Row(
|
||||
// children: [
|
||||
// const Icon(
|
||||
// Icons.search,
|
||||
// color: Color.fromARGB(137, 180, 172, 172),
|
||||
// size: 18,
|
||||
// ),
|
||||
// const SizedBox(width: 8),
|
||||
// Expanded(
|
||||
// child: TextField(
|
||||
// controller: searchController,
|
||||
// onChanged: filterSearch, // Memanggil filter saat mengetik
|
||||
// style: const TextStyle(color: Colors.black, fontSize: 14),
|
||||
// decoration: const InputDecoration(
|
||||
// border: InputBorder.none,
|
||||
// hintText: 'Cari pelajaran...',
|
||||
// hintStyle: TextStyle(color: Colors.black54),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// if (searchController.text.isNotEmpty)
|
||||
// IconButton(
|
||||
// icon: const Icon(Icons.close, color: Colors.black54),
|
||||
// onPressed: () {
|
||||
// searchController.clear();
|
||||
// filterSearch('');
|
||||
// },
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// Collection Items dengan Filter
|
||||
SizedBox(height: 20),
|
||||
Expanded(
|
||||
Container headerContainer(
|
||||
BuildContext context,
|
||||
List<Map<String, dynamic>> filteredCollections,
|
||||
) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(225, 79, 76, 182),
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(25),
|
||||
bottomRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
|
||||
child: Container(
|
||||
height: 47,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(224, 145, 145, 155),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(25),
|
||||
topRight: Radius.circular(25),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: Colors.grey.shade200,
|
||||
),
|
||||
child: Column(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(height: 5),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width / 4,
|
||||
),
|
||||
child: Divider(
|
||||
color: const Color.fromARGB(255, 155, 147, 147),
|
||||
height: 20,
|
||||
),
|
||||
const Icon(
|
||||
Icons.search,
|
||||
color: Color.fromARGB(137, 180, 172, 172),
|
||||
size: 18,
|
||||
),
|
||||
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
// vertical: 10,
|
||||
child: TextField(
|
||||
controller: searchController,
|
||||
onChanged: filterSearch, // Memanggil filter saat mengetik
|
||||
style: const TextStyle(color: Colors.black, fontSize: 14),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Cari pelajaran...',
|
||||
hintStyle: TextStyle(color: Colors.black54),
|
||||
),
|
||||
child:
|
||||
filteredItems.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
"Tidak ada hasil",
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 20.0,
|
||||
left: 20,
|
||||
right: 20,
|
||||
),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: const Color.fromARGB(
|
||||
255,
|
||||
222,
|
||||
181,
|
||||
133,
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12,
|
||||
right: 20,
|
||||
left: 20,
|
||||
),
|
||||
child: GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 1, // 2 Kolom
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
childAspectRatio: 2.5,
|
||||
),
|
||||
itemCount: filteredItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
child: CardList(),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(
|
||||
context,
|
||||
) => ListDetailScreen(
|
||||
title:
|
||||
filteredItems[index],
|
||||
imagePath:
|
||||
filteredItems[index],
|
||||
paragraphs: [
|
||||
"""
|
||||
<h1>Enter the main heading, usually the same as the title.</h1>
|
||||
<p>Be <b>bold</b> in stating your key points. Put them in a list: </p>
|
||||
<ul>
|
||||
<li>The first item in your list</li>
|
||||
<li>The second item; <i>italicize</i> key words</li>
|
||||
</ul>
|
||||
<p>Improve your image by including an image. </p>
|
||||
<p><img src="https://via.placeholder.com/150" alt="A Great HTML Resource"></p>
|
||||
<p>Add a link to your favorite <a href="https://www.dummies.com/">Web site</a>.</p>
|
||||
<hr>
|
||||
<p>Finally, link to <a href="page2.html">another page</a> in your own Web site.</p>
|
||||
<p>© Wiley Publishing, 2011</p>
|
||||
""",
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (searchController.text.isNotEmpty)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close, color: Colors.black54),
|
||||
onPressed: () {
|
||||
searchController.clear();
|
||||
filterSearch('');
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Bottom Navigation
|
||||
// MainMenu(),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children:
|
||||
filteredCollections.map((item) {
|
||||
final isSelected = selectedCategory == item["category"];
|
||||
return CollectionContainer(
|
||||
label: item["label"]!,
|
||||
imageSvg: item["image"]!,
|
||||
iconw: 20,
|
||||
iconh: 20,
|
||||
width: 70,
|
||||
height: 70,
|
||||
textColor:
|
||||
isSelected
|
||||
? const Color.fromARGB(255, 230, 48, 160)
|
||||
: Colors.black,
|
||||
lblSize: 10,
|
||||
// colorContiner: ColorHelper.hexToColor(item["color"]),
|
||||
colorContiner:
|
||||
isSelected
|
||||
? const Color.fromARGB(255, 84, 3, 224)
|
||||
: ColorHelper.hexToColor(item["color"]),
|
||||
onTapAc:
|
||||
() => {
|
||||
searchController.clear(),
|
||||
filterByCategory(item["category"]),
|
||||
},
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Container contentContainer(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(223, 200, 200, 206),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(25),
|
||||
topRight: Radius.circular(25),
|
||||
),
|
||||
),
|
||||
child: _listCard(context),
|
||||
);
|
||||
}
|
||||
|
||||
Column _listCard(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(height: 5),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: MediaQuery.of(context).size.width / 4,
|
||||
),
|
||||
child: Divider(
|
||||
color: const Color.fromARGB(255, 255, 255, 255),
|
||||
thickness: 2,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
// vertical: 10,
|
||||
),
|
||||
child:
|
||||
filteredItems.isEmpty
|
||||
? const Center(
|
||||
child: Text(
|
||||
"Tidak ada hasil",
|
||||
style: TextStyle(fontSize: 16, color: Colors.black54),
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: EdgeInsets.only(top: 10.0, left: 10, right: 10),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
color: const Color.fromARGB(255, 222, 181, 133),
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12,
|
||||
right: 20,
|
||||
left: 20,
|
||||
),
|
||||
child: _generateList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
GridView _generateList() {
|
||||
return GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 1,
|
||||
crossAxisSpacing: 10,
|
||||
mainAxisSpacing: 10,
|
||||
childAspectRatio: 2.5,
|
||||
),
|
||||
itemCount: filteredItems.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
child: CardList(
|
||||
title: filteredItems[index]["title"] ?? "",
|
||||
body: filteredItems[index]["body"] ?? "",
|
||||
gambar: filteredItems[index]['image'],
|
||||
point: filteredItems[index]['point'],
|
||||
coint: filteredItems[index]['coint'],
|
||||
tipe: filteredItems[index]['type'],
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
// (context) => listDetailExtension.buildPage({
|
||||
// 'title': filteredItems[index]['category'],
|
||||
// 'imagePath': filteredItems[index]['image'],
|
||||
// 'filepath': filteredItems[index]['file'],
|
||||
// 'paragraphs':
|
||||
// """
|
||||
// """
|
||||
// "<h1> ${filteredItems[index]["title"]} </h1> <p> ${filteredItems[index]["body"]}",
|
||||
// }),
|
||||
(context) => ListDetailScreen(
|
||||
title: filteredItems[index]['category'],
|
||||
// 'imagePath': filteredItems[index]['image'],
|
||||
filepath: filteredItems[index]['file'],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekakes/components/buildcard_info.dart';
|
||||
import 'package:freekake/components/buildcard_info.dart';
|
||||
import 'package:freekake/components/collection_container.dart';
|
||||
|
||||
class DetailScreen extends StatefulWidget {
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
|
||||
class ListDetailScreen extends StatefulWidget {
|
||||
class ListPustakaDetailScreen extends StatefulWidget {
|
||||
final String title;
|
||||
final String imagePath;
|
||||
final List<String> paragraphs;
|
||||
final String paragraphs;
|
||||
final String filepath;
|
||||
|
||||
const ListDetailScreen({
|
||||
Key? key,
|
||||
const ListPustakaDetailScreen({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.imagePath,
|
||||
required this.paragraphs,
|
||||
}) : super(key: key);
|
||||
required this.filepath,
|
||||
});
|
||||
|
||||
@override
|
||||
_ListDetailScreenState createState() => _ListDetailScreenState();
|
||||
}
|
||||
|
||||
class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
class _ListDetailScreenState extends State<ListPustakaDetailScreen> {
|
||||
late List<bool> _readStatus;
|
||||
String _htmlContent = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_readStatus = List.generate(widget.paragraphs.length, (index) => false);
|
||||
loadHtml();
|
||||
}
|
||||
|
||||
void _toggleReadStatus(int index) {
|
||||
@@ -32,6 +37,20 @@ class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> loadHtml() async {
|
||||
String html = await DefaultAssetBundle.of(
|
||||
context,
|
||||
).loadString(widget.filepath);
|
||||
|
||||
html = html.replaceAllMapped(
|
||||
RegExp(r'<div class="gizi-grid">(.*?)</div>', dotAll: true),
|
||||
(match) => '<gizi-grid>${match.group(1)}</gizi-grid>',
|
||||
);
|
||||
setState(() {
|
||||
_htmlContent = html;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -55,96 +74,11 @@ class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.asset(
|
||||
widget.imagePath,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.title,
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 0),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18.0),
|
||||
child: ListView.builder(
|
||||
itemCount: widget.paragraphs.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child:
|
||||
// Text(
|
||||
// widget.paragraphs[index] +
|
||||
// style: TextStyle(color: Colors.black),
|
||||
Html(
|
||||
style: {"body": Style(color: Colors.black)},
|
||||
data:
|
||||
widget.paragraphs.toString() +
|
||||
"""
|
||||
<html>
|
||||
<!-- Text between angle brackets is an HTML tag and is not displayed.
|
||||
Most tags, such as the HTML and /HTML tags that surround the contents of
|
||||
a page, come in pairs; some tags, like HR, for a horizontal rule, stand
|
||||
alone. Comments, such as the text you're reading, are not displayed when
|
||||
the Web page is shown. The information between the HEAD and /HEAD tags is
|
||||
not displayed. The information between the BODY and /BODY tags is displayed.-->
|
||||
<head>
|
||||
<title>Enter a title, displayed at the top of the window.</title>
|
||||
</head>
|
||||
<!-- The information between the BODY and /BODY tags is displayed.-->
|
||||
<body>
|
||||
<h1>Enter the main heading, usually the same as the title.</h1>
|
||||
<p>Be <b>bold</b> in stating your key points. Put them in a list: </p>
|
||||
<ul>
|
||||
<li>The first item in your list</li>
|
||||
<li>The second item; <i>italicize</i> key words</li>
|
||||
</ul>
|
||||
<p>Improve your image by including an image. </p>
|
||||
<p><img src="http://www.mygifs.com/CoverImage.gif" alt="A Great HTML Resource"></p>
|
||||
<p>Add a link to your favorite <a href="https://www.dummies.com/">Web site</a>.
|
||||
Break up your page with a horizontal rule or two. </p>
|
||||
<hr>
|
||||
<p>Finally, link to <a href="page2.html">another page</a> in your own Web site.</p>
|
||||
<!-- And add a copyright notice.-->
|
||||
<p>© Wiley Publishing, 2011</p>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
),
|
||||
),
|
||||
// ),
|
||||
// Checkbox(
|
||||
// value: _readStatus[index],
|
||||
// onChanged: (bool? value) {
|
||||
// _toggleReadStatus(index);
|
||||
// },
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(left: 18.0, right: 18, bottom: 18),
|
||||
child: htmlBody(),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -152,4 +86,71 @@ class _ListDetailScreenState extends State<ListDetailScreen> {
|
||||
// ),
|
||||
);
|
||||
}
|
||||
|
||||
Html htmlBody() {
|
||||
return Html(
|
||||
style: {"body": Style(color: const Color.fromRGBO(0, 0, 0, 1))},
|
||||
// data: widget.paragraphs,
|
||||
data: _htmlContent,
|
||||
extensions: [
|
||||
TagExtension(
|
||||
tagsToExtend: {"img"},
|
||||
builder: (ExtensionContext context) {
|
||||
final attrs = context.attributes;
|
||||
final src = attrs['src'] ?? '';
|
||||
|
||||
if (src.isNotEmpty) {
|
||||
if (src.startsWith('assets/html/')) {
|
||||
return Image.asset(
|
||||
src,
|
||||
width: 100,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
const Text('Image not found'),
|
||||
);
|
||||
} else if (!src.startsWith('http')) {
|
||||
return Image.asset(
|
||||
'assets/images/$src',
|
||||
width: 100,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
const Text('Image not found'),
|
||||
);
|
||||
} else {
|
||||
return Image.network(
|
||||
src,
|
||||
width: 100,
|
||||
errorBuilder:
|
||||
(context, error, stackTrace) =>
|
||||
const Text('Image failed to load'),
|
||||
);
|
||||
}
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
),
|
||||
TagExtension(
|
||||
tagsToExtend: {"gizi-grid"},
|
||||
builder: (context) {
|
||||
final spans = context.inlineSpanChildren ?? [];
|
||||
|
||||
final widgets =
|
||||
spans.map((span) {
|
||||
if (span is WidgetSpan) return span.child;
|
||||
return const SizedBox();
|
||||
}).toList();
|
||||
|
||||
return Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children:
|
||||
widgets.map((child) {
|
||||
return SizedBox(width: 160, child: child);
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:furibase/components/buildcard_info.dart';
|
||||
import 'package:furibase/components/collection_container.dart';
|
||||
import 'package:furibase/components/menu_button.dart';
|
||||
import 'package:furibase/components/scan_button.dart';
|
||||
import 'package:furibase/screen/Home_screen.dart';
|
||||
import 'package:furibase/screen/pustaka/list_education.dart';
|
||||
import 'package:freekake/components/buildcard_info.dart';
|
||||
import 'package:freekake/components/collection_container.dart';
|
||||
import 'package:freekake/components/menu_button.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/screen/Home_screen.dart';
|
||||
import 'package:freekake/screen/pustaka/list_education.dart';
|
||||
|
||||
class PustakaScreen extends StatefulWidget {
|
||||
const PustakaScreen({super.key});
|
||||
@@ -108,7 +108,7 @@ class _PustakaScreenState extends State<PustakaScreen> {
|
||||
children: [
|
||||
CollectionContainer(
|
||||
label: "Luffy",
|
||||
imagesrc: 'assets/images/luffy.png',
|
||||
imagesrc: 'images/luffy.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
onTapAc:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:furibase/components/buildcard_info.dart';
|
||||
import 'package:furibase/components/collection_container.dart';
|
||||
import 'package:furibase/components/menu_button.dart';
|
||||
import 'package:furibase/components/scan_button.dart';
|
||||
import 'package:furibase/screen/Home_screen.dart';
|
||||
import 'package:furibase/screen/pustaka/list_education.dart';
|
||||
import 'package:freekake/components/buildcard_info.dart';
|
||||
import 'package:freekake/components/collection_container.dart';
|
||||
import 'package:freekake/components/menu_button.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/screen/Home_screen.dart';
|
||||
import 'package:freekake/screen/pustaka/list_education.dart';
|
||||
|
||||
class PustakaScreen extends StatefulWidget {
|
||||
const PustakaScreen({super.key});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:furibase/components/buildcard_info.dart';
|
||||
import 'package:furibase/components/collection_container.dart';
|
||||
import 'package:furibase/components/menu_button.dart';
|
||||
import 'package:furibase/components/scan_button.dart';
|
||||
import 'package:furibase/screen/Home_screen.dart';
|
||||
import 'package:furibase/screen/pustaka/list_education.dart';
|
||||
import 'package:freekake/components/buildcard_info.dart';
|
||||
import 'package:freekake/components/collection_container.dart';
|
||||
import 'package:freekake/components/menu_button.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/screen/Home_screen.dart';
|
||||
import 'package:freekake/screen/pustaka/list_education.dart';
|
||||
|
||||
class PustakaScreen extends StatefulWidget {
|
||||
const PustakaScreen({super.key});
|
||||
@@ -108,7 +108,7 @@ class _PustakaScreenState extends State<PustakaScreen> {
|
||||
children: [
|
||||
CollectionContainer(
|
||||
label: "Luffy",
|
||||
imagesrc: 'assets/images/luffy.png',
|
||||
imagesrc: 'images/luffy.png',
|
||||
width: 100,
|
||||
height: 100,
|
||||
onTapAc:
|
||||
|
||||
@@ -44,6 +44,32 @@ class _PustakaScreenState extends State<PustakaScreen> {
|
||||
},
|
||||
];
|
||||
|
||||
final List<Map<String, dynamic>> _genres = [
|
||||
{
|
||||
"label": "Game",
|
||||
"type": "game",
|
||||
"image": "assets/icons/healthy.svg",
|
||||
"color": "#cdd0ee",
|
||||
},
|
||||
{
|
||||
"label": "Novel",
|
||||
"image": "assets/icons/Nutrition.svg",
|
||||
"color": "#e8e29a",
|
||||
},
|
||||
{
|
||||
"label": "TTS",
|
||||
"type": "puzzle",
|
||||
"image": "assets/icons/Education.svg",
|
||||
"color": "#efd8c6",
|
||||
},
|
||||
{
|
||||
"label": "Pazzle",
|
||||
"type": "puzzle",
|
||||
"image": "assets/icons/Safety.svg",
|
||||
"color": "#cef1da",
|
||||
},
|
||||
];
|
||||
|
||||
void _onMenuTapped(int index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
@@ -57,250 +83,264 @@ class _PustakaScreenState extends State<PustakaScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List<Map<String, dynamic>> filteredCollections =
|
||||
_collections
|
||||
.where(
|
||||
(item) => item["label"]!.toLowerCase().contains(
|
||||
_searchQuery.toLowerCase(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final buttonScanSize = screenWidth * 0.20;
|
||||
final double bottomPadding = (9000 / screenWidth).clamp(0, 0.2);
|
||||
|
||||
List<Map<String, dynamic>> filterCollections(
|
||||
List<Map<String, dynamic>> collections,
|
||||
String query,
|
||||
) {
|
||||
return collections
|
||||
.where(
|
||||
(item) =>
|
||||
item["label"]!.toLowerCase().contains(query.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> filteredCollections = filterCollections(
|
||||
_collections,
|
||||
_searchQuery,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Stack(
|
||||
children: [
|
||||
//Top Bar
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 130, child: TopbarContainer()),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
// Header Section
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
color: const Color.fromARGB(
|
||||
250,
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
).withOpacity(0.0),
|
||||
body: pustakaBody(
|
||||
filteredCollections,
|
||||
context,
|
||||
bottomPadding,
|
||||
screenWidth,
|
||||
buttonScanSize,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Stack pustakaBody(
|
||||
List<Map<String, dynamic>> filteredCollections,
|
||||
BuildContext context,
|
||||
double bottomPadding,
|
||||
double screenWidth,
|
||||
double buttonScanSize,
|
||||
) {
|
||||
return Stack(
|
||||
children: [
|
||||
//Top Bar
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 130, child: TopbarContainer()),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
// Header Section
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
color: const Color.fromARGB(250, 255, 255, 255).withOpacity(0.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BuildcardInfo(
|
||||
icon: Icons.person,
|
||||
text: 'User01',
|
||||
extraIcon: Icons.emoji_events,
|
||||
extraText: "5000",
|
||||
width: 180,
|
||||
),
|
||||
BuildcardInfo(
|
||||
icon: Icons.monetization_on,
|
||||
text: "1.300",
|
||||
width: 90,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Search Bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10),
|
||||
child: Container(
|
||||
height: 47,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: const Color.fromARGB(255, 237, 227, 227),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
BuildcardInfo(
|
||||
icon: Icons.person,
|
||||
text: 'User01',
|
||||
extraIcon: Icons.emoji_events,
|
||||
extraText: "5000",
|
||||
width: 180,
|
||||
),
|
||||
BuildcardInfo(
|
||||
icon: Icons.monetization_on,
|
||||
text: "1.300",
|
||||
width: 90,
|
||||
const Icon(Icons.search, color: Colors.black54, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Search...',
|
||||
hintStyle: TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// // Date Display
|
||||
// Padding(
|
||||
// padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
// child: Text(
|
||||
// DateTime.now().toString(),
|
||||
// style: const TextStyle(color: Colors.white),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
// Search Bar
|
||||
Padding(
|
||||
),
|
||||
// Collection Items
|
||||
SizedBox(
|
||||
height: 440,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 40,
|
||||
vertical: 10,
|
||||
horizontal: 58,
|
||||
vertical: 60,
|
||||
),
|
||||
child: Container(
|
||||
height: 47,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: const Color.fromARGB(255, 237, 227, 227),
|
||||
child: GridView.builder(
|
||||
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 140,
|
||||
mainAxisSpacing: 20,
|
||||
crossAxisSpacing: 20,
|
||||
childAspectRatio: 1, // 1:1 rasio (kotak)
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||
itemCount: filteredCollections.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = filteredCollections[index];
|
||||
return CollectionContainer(
|
||||
label: item["label"]!,
|
||||
imageSvg: item["image"]!,
|
||||
width: 30,
|
||||
height: 80,
|
||||
textColor: Colors.black,
|
||||
circle: true,
|
||||
colorContiner: ColorHelper.hexToColor(item["color"]),
|
||||
onTapAc: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder:
|
||||
(_) => ListEducation(
|
||||
selectedCategory: item["label"],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// SizedBox(
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 5,
|
||||
// vertical: 1,
|
||||
// ),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text("List Topic", style: TextStyle(color: Colors.black)),
|
||||
// Divider(color: Colors.transparent),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Horizontal Scroll List
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 100,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.search, color: Colors.black54, size: 18),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
});
|
||||
},
|
||||
style: const TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 12,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
border: InputBorder.none,
|
||||
hintText: 'Cari konten...',
|
||||
hintStyle: TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// Collection Items
|
||||
Container(
|
||||
height: 440,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 58,
|
||||
vertical: 60,
|
||||
),
|
||||
child: GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 140,
|
||||
mainAxisSpacing: 20,
|
||||
crossAxisSpacing: 20,
|
||||
childAspectRatio: 1, // 1:1 rasio (kotak)
|
||||
),
|
||||
itemCount: filteredCollections.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = filteredCollections[index];
|
||||
return CollectionContainer(
|
||||
label: item["label"]!,
|
||||
lblSize: 11,
|
||||
imageSvg: item["image"]!,
|
||||
width: 30,
|
||||
height: 80,
|
||||
textColor: Colors.black,
|
||||
circle: true,
|
||||
colorContiner: ColorHelper.hexToColor(item["color"]),
|
||||
onTapAc: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => const ListEducation(),
|
||||
children:
|
||||
_genres.map((genre) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// SizedBox(
|
||||
// child: Padding(
|
||||
// padding: const EdgeInsets.symmetric(
|
||||
// horizontal: 5,
|
||||
// vertical: 1,
|
||||
// ),
|
||||
// child: Column(
|
||||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||||
// children: [
|
||||
// Text("List Topic", style: TextStyle(color: Colors.black)),
|
||||
// Divider(color: Colors.transparent),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// Horizontal Scroll List
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 5, vertical: 5),
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: 100,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Row(
|
||||
children: List.generate(
|
||||
10,
|
||||
(index) => Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
elevation: 5,
|
||||
color: ColorHelper.hexToColor("#efd8c6"),
|
||||
// color: const Color.fromARGB(140, 255, 255, 255),
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(15),
|
||||
// ),
|
||||
child: GestureDetector(
|
||||
onTap:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ListEducation(),
|
||||
child: Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
),
|
||||
elevation: 5,
|
||||
color: ColorHelper.hexToColor("#efd8c6"),
|
||||
// color: const Color.fromARGB(140, 255, 255, 255),
|
||||
// shape: RoundedRectangleBorder(
|
||||
// borderRadius: BorderRadius.circular(15),
|
||||
// ),
|
||||
child: GestureDetector(
|
||||
onTap:
|
||||
() => {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => ListEducation(),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
child: Container(
|
||||
width: 80,
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.book,
|
||||
size: 50,
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
Text(
|
||||
'Topik ' + (index + 1).toString(),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 11,
|
||||
color: Color.fromARGB(255, 0, 0, 0),
|
||||
},
|
||||
child: Container(
|
||||
width: 80,
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.book,
|
||||
size: 50,
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
),
|
||||
],
|
||||
Text(
|
||||
genre['label'],
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color.fromARGB(255, 0, 0, 0),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Bottom Navigation
|
||||
// MainMenu(),
|
||||
],
|
||||
),
|
||||
// BG MEnu
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 85, child: BottomNavbar()),
|
||||
),
|
||||
Positioned(bottom: 0, left: 0, right: 0, child: MainMenu()),
|
||||
],
|
||||
),
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
left: MediaQuery.of(context).size.width / 2 - 43,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -20),
|
||||
child: ScanButton(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
// BG MEnu
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: SizedBox(height: 85, child: BottomNavbar()),
|
||||
),
|
||||
Positioned(bottom: 0, left: 0, right: 0, child: MainMenu()),
|
||||
],
|
||||
),
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: bottomPadding * 98,
|
||||
left: (screenWidth - buttonScanSize) / 2,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -20),
|
||||
child: ScanButton(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
|
||||
// import 'package:image_picker_web/image_picker_web.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
class AkunSaya extends StatefulWidget {
|
||||
const AkunSaya({super.key});
|
||||
|
||||
@override
|
||||
_AkunSayaState createState() => _AkunSayaState();
|
||||
}
|
||||
@@ -17,6 +20,8 @@ class AkunSaya extends StatefulWidget {
|
||||
class _AkunSayaState extends State<AkunSaya> {
|
||||
dynamic _profileImage;
|
||||
dynamic _headerImage;
|
||||
ImageProvider? imageProvider;
|
||||
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
Future<void> _pickImage(bool isProfile) async {
|
||||
@@ -34,13 +39,19 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
} else {
|
||||
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
final imageFile = File(pickedFile.path);
|
||||
setState(() {
|
||||
if (isProfile) {
|
||||
_profileImage = File(pickedFile.path);
|
||||
_profileImage = imageFile;
|
||||
} else {
|
||||
_headerImage = File(pickedFile.path);
|
||||
_headerImage = imageFile;
|
||||
}
|
||||
|
||||
imageProvider =
|
||||
kIsWeb ? MemoryImage(_profileImage) : FileImage(_profileImage);
|
||||
});
|
||||
} else {
|
||||
imageProvider = AssetImage("assets/images/luffy.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,20 +63,19 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
title: Text("Saya"),
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
),
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0, left: 20, right: 20),
|
||||
padding: const EdgeInsets.only(top: 0.0, left: 20, right: 20),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 205, 202, 189),
|
||||
color: const Color.fromARGB(255, 212, 211, 208),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
@@ -89,7 +99,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
SizedBox(height: 70),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
@@ -97,7 +107,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 40),
|
||||
children: [
|
||||
TextFormField(
|
||||
initialValue: "Cepot",
|
||||
initialValue: "Luffy Kun",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Nama',
|
||||
@@ -106,7 +116,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "cpt09",
|
||||
initialValue: "luffy01",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Nama Pengguna',
|
||||
@@ -115,16 +125,15 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
|
||||
TextFormField(
|
||||
initialValue: "Jl. Sentosa jaya",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Alamat',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
|
||||
// TextFormField(
|
||||
// initialValue: "Jl. Sentosa jaya",
|
||||
// decoration: const InputDecoration(
|
||||
// border: UnderlineInputBorder(),
|
||||
// labelText: 'Alamat',
|
||||
// labelStyle: TextStyle(color: Colors.black),
|
||||
// ),
|
||||
// style: TextStyle(color: Colors.black),
|
||||
// ),
|
||||
TextFormField(
|
||||
initialValue: "******",
|
||||
decoration: const InputDecoration(
|
||||
@@ -134,15 +143,6 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "+62923",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'No. HP',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -169,7 +169,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
: null,
|
||||
child:
|
||||
_profileImage == null
|
||||
? Icon(Icons.person, size: 50, color: Colors.white)
|
||||
? Image.asset("assets/images/luffy.png")
|
||||
: null,
|
||||
),
|
||||
CircleAvatar(
|
||||
@@ -184,14 +184,14 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
|
||||
Positioned(
|
||||
top: 130,
|
||||
left: MediaQuery.of(context).size.width / 2 - 50,
|
||||
left: MediaQuery.of(context).size.width / 2 - 30,
|
||||
child: GestureDetector(
|
||||
onTap: () => _pickImage(true),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
Text(
|
||||
"Nama Akun",
|
||||
"Luffy kun",
|
||||
style: TextStyle(
|
||||
color: Colors.deepPurple,
|
||||
fontSize: 18,
|
||||
|
||||
@@ -1,34 +1,58 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:furibase/components/bottom_navbar.dart';
|
||||
import 'package:furibase/components/main_menu.dart';
|
||||
import 'package:furibase/components/scan_button.dart';
|
||||
import 'package:furibase/helpers/pick_image.dart';
|
||||
import 'package:freekake/components/bottom_navbar.dart';
|
||||
import 'package:freekake/components/main_menu.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
|
||||
// import 'package:image_picker_web/image_picker_web.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
class AkunSaya extends StatefulWidget {
|
||||
class DataSaya extends StatefulWidget {
|
||||
const DataSaya({super.key});
|
||||
|
||||
@override
|
||||
_AkunSayaState createState() => _AkunSayaState();
|
||||
_DataSayaState createState() => _DataSayaState();
|
||||
}
|
||||
|
||||
class _AkunSayaState extends State<AkunSaya> {
|
||||
class _DataSayaState extends State<DataSaya> {
|
||||
dynamic _profileImage;
|
||||
dynamic _headerImage;
|
||||
//final ImagePicker _picker = ImagePicker();
|
||||
ImageProvider? imageProvider;
|
||||
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
Future<void> _pickImage(bool isProfile) async {
|
||||
final imagePicker = ImagePickerHelper();
|
||||
dynamic pic = await imagePicker.pickImage();
|
||||
if (kIsWeb) {
|
||||
// Uint8List? bytesFromPicker = await ImagePickerWeb.getImageAsBytes();
|
||||
// if (bytesFromPicker != null) {
|
||||
// setState(() {
|
||||
// if (isProfile) {
|
||||
// _profileImage = bytesFromPicker;
|
||||
// } else {
|
||||
// _headerImage = bytesFromPicker;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
} else {
|
||||
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
final imageFile = File(pickedFile.path);
|
||||
setState(() {
|
||||
if (isProfile) {
|
||||
_profileImage = imageFile;
|
||||
} else {
|
||||
_headerImage = imageFile;
|
||||
}
|
||||
|
||||
if (pic != null) {
|
||||
setState(() {
|
||||
if (isProfile) {
|
||||
_profileImage = pic;
|
||||
} else {
|
||||
_headerImage = pic;
|
||||
}
|
||||
});
|
||||
imageProvider =
|
||||
kIsWeb ? MemoryImage(_profileImage) : FileImage(_profileImage);
|
||||
});
|
||||
} else {
|
||||
imageProvider = AssetImage("assets/images/luffy.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,23 +60,22 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Saya"),
|
||||
title: Text("Data Pribadi"),
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
),
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 20.0, left: 20, right: 20),
|
||||
padding: const EdgeInsets.only(top: 0.0, left: 20, right: 20),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 205, 202, 189),
|
||||
color: const Color.fromARGB(255, 212, 211, 208),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
@@ -63,7 +86,6 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
// Header image (bisa diubah)
|
||||
SizedBox(
|
||||
height: 80,
|
||||
child: Container(
|
||||
@@ -76,7 +98,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 40),
|
||||
SizedBox(height: 70),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(20),
|
||||
@@ -84,48 +106,75 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 40),
|
||||
children: [
|
||||
TextFormField(
|
||||
initialValue: "Cepot",
|
||||
initialValue: "Hasan",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Nama',
|
||||
labelText: 'Nama Siswa',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "cpt09",
|
||||
initialValue: "Jawa Barat",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Nama Pengguna',
|
||||
labelText: 'Provinsi',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
|
||||
TextFormField(
|
||||
initialValue: "Jl. Sentosa jaya",
|
||||
initialValue: "Bandung",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Alamat',
|
||||
labelText: 'Kabupaten',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
|
||||
TextFormField(
|
||||
initialValue: "******",
|
||||
initialValue: "SDN 1 Bandung Barat",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Kata Sandi',
|
||||
labelText: 'Sekolah',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "+62923",
|
||||
initialValue: "Laki-laki",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'No. HP',
|
||||
labelText: 'Jenis Kelamin',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "Bandung 13-05-2025",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Sekolah',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "56 Kg",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Berat Badan',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: "170",
|
||||
decoration: const InputDecoration(
|
||||
border: UnderlineInputBorder(),
|
||||
labelText: 'Tinggi Badan',
|
||||
labelStyle: TextStyle(color: Colors.black),
|
||||
),
|
||||
style: TextStyle(color: Colors.black),
|
||||
@@ -156,7 +205,7 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
: null,
|
||||
child:
|
||||
_profileImage == null
|
||||
? Icon(Icons.person, size: 50, color: Colors.white)
|
||||
? Image.asset("assets/images/luffy.png")
|
||||
: null,
|
||||
),
|
||||
CircleAvatar(
|
||||
@@ -171,14 +220,14 @@ class _AkunSayaState extends State<AkunSaya> {
|
||||
|
||||
Positioned(
|
||||
top: 130,
|
||||
left: MediaQuery.of(context).size.width / 2 - 50,
|
||||
left: MediaQuery.of(context).size.width / 2 - 30,
|
||||
child: GestureDetector(
|
||||
onTap: () => _pickImage(true),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
Text(
|
||||
"Nama Akun",
|
||||
"Luffy kun",
|
||||
style: TextStyle(
|
||||
color: Colors.deepPurple,
|
||||
fontSize: 18,
|
||||
@@ -0,0 +1,305 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freekake/components/bottom_navbar.dart';
|
||||
import 'package:freekake/components/main_menu.dart';
|
||||
import 'package:freekake/components/point_header.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
|
||||
// import 'package:image_picker_web/image_picker_web.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
class PointSaya extends StatefulWidget {
|
||||
const PointSaya({super.key});
|
||||
|
||||
@override
|
||||
_PointSayaState createState() => _PointSayaState();
|
||||
}
|
||||
|
||||
class _PointSayaState extends State<PointSaya> {
|
||||
dynamic _profileImage;
|
||||
dynamic _headerImage;
|
||||
ImageProvider? imageProvider;
|
||||
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
Future<void> _pickImage(bool isProfile) async {
|
||||
if (kIsWeb) {
|
||||
// Uint8List? bytesFromPicker = await ImagePickerWeb.getImageAsBytes();
|
||||
// if (bytesFromPicker != null) {
|
||||
// setState(() {
|
||||
// if (isProfile) {
|
||||
// _profileImage = bytesFromPicker;
|
||||
// } else {
|
||||
// _headerImage = bytesFromPicker;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
} else {
|
||||
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
final imageFile = File(pickedFile.path);
|
||||
setState(() {
|
||||
if (isProfile) {
|
||||
_profileImage = imageFile;
|
||||
} else {
|
||||
_headerImage = imageFile;
|
||||
}
|
||||
|
||||
imageProvider =
|
||||
kIsWeb ? MemoryImage(_profileImage) : FileImage(_profileImage);
|
||||
});
|
||||
} else {
|
||||
imageProvider = AssetImage("assets/images/luffy.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Point Saya"),
|
||||
backgroundColor: Color.fromARGB(225, 79, 76, 182),
|
||||
),
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Stack(
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 0.0, left: 20, right: 20),
|
||||
child: Container(
|
||||
height: MediaQuery.of(context).size.height * 0.7,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(150, 137, 123, 69),
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
children: [
|
||||
SizedBox(height: 4),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 10,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 191, 184, 184),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
boxShadow: [
|
||||
BoxShadow(color: Colors.black12, blurRadius: 8),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
SizedBox(height: 5),
|
||||
Row(
|
||||
children: [
|
||||
// Image.asset(
|
||||
// 'assets/icons/diamond.png',
|
||||
// width: 24,
|
||||
// ),
|
||||
Text(
|
||||
"Total Point",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"300",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
// Image.asset('assets/icons/coin.png', width: 24),
|
||||
Text(
|
||||
"Total Koin",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
"60",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(padding: EdgeInsets.all(8)),
|
||||
Container(
|
||||
width: MediaQuery.of(context).size.width * 0.8,
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Riwayat poin dan koin",
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white60,
|
||||
),
|
||||
),
|
||||
),
|
||||
// color: Colors.amber,
|
||||
),
|
||||
],
|
||||
),
|
||||
PointsHeader(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(width: 40),
|
||||
Text("Furikake", style: TextStyle(color: Colors.white)),
|
||||
SizedBox(width: 130),
|
||||
Text(
|
||||
"",
|
||||
// "🔥 Level: ${points.level}",
|
||||
// style: TextStyle(color: Colors.orangeAccent),
|
||||
),
|
||||
Text("100", style: TextStyle(color: Colors.lightBlue)),
|
||||
SizedBox(width: 60),
|
||||
Text("20", style: TextStyle(color: Colors.greenAccent)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(width: 40),
|
||||
Text("Furikake", style: TextStyle(color: Colors.white)),
|
||||
SizedBox(width: 130),
|
||||
Text(
|
||||
"",
|
||||
// "🔥 Level: ${points.level}",
|
||||
// style: TextStyle(color: Colors.orangeAccent),
|
||||
),
|
||||
Text("100", style: TextStyle(color: Colors.lightBlue)),
|
||||
SizedBox(width: 60),
|
||||
Text("20", style: TextStyle(color: Colors.greenAccent)),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(width: 40),
|
||||
Text("Furikake", style: TextStyle(color: Colors.white)),
|
||||
SizedBox(width: 130),
|
||||
Text(
|
||||
"",
|
||||
// "🔥 Level: ${points.level}",
|
||||
// style: TextStyle(color: Colors.orangeAccent),
|
||||
),
|
||||
Text("100", style: TextStyle(color: Colors.lightBlue)),
|
||||
SizedBox(width: 60),
|
||||
Text("20", style: TextStyle(color: Colors.greenAccent)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// Profile Picture (bisa diubah)
|
||||
Positioned(
|
||||
top: 25,
|
||||
left: MediaQuery.of(context).size.width / 2 - 50,
|
||||
child: GestureDetector(
|
||||
onTap: () => _pickImage(true),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Colors.grey[300],
|
||||
backgroundImage:
|
||||
_profileImage != null
|
||||
? (kIsWeb
|
||||
? MemoryImage(_profileImage) as ImageProvider
|
||||
: FileImage(_profileImage) as ImageProvider)
|
||||
: null,
|
||||
child:
|
||||
_profileImage == null
|
||||
? Image.asset("assets/images/luffy.png")
|
||||
: null,
|
||||
),
|
||||
CircleAvatar(
|
||||
radius: 15,
|
||||
backgroundColor: Colors.blue,
|
||||
child: Icon(Icons.edit, color: Colors.white, size: 15),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
top: 130,
|
||||
left: MediaQuery.of(context).size.width / 2 - 30,
|
||||
child: GestureDetector(
|
||||
onTap: () => _pickImage(true),
|
||||
child: Stack(
|
||||
alignment: Alignment.bottomRight,
|
||||
children: [
|
||||
Text(
|
||||
"Luffy kun",
|
||||
style: TextStyle(
|
||||
color: Colors.deepPurple,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListItem(IconData icon, String label, {bool isLogout = false}) {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
icon,
|
||||
color:
|
||||
isLogout
|
||||
? const Color.fromARGB(255, 181, 47, 47)
|
||||
: const Color.fromARGB(255, 255, 255, 255),
|
||||
),
|
||||
title: Text(label, style: TextStyle(fontSize: 16, color: Colors.black)),
|
||||
trailing: Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onTap: () {},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,10 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:furibase/components/bottom_navbar.dart';
|
||||
import 'package:furibase/components/main_menu.dart';
|
||||
import 'package:furibase/components/scan_button.dart';
|
||||
import 'package:furibase/screen/saya/akun_saya.dart';
|
||||
import 'package:freekake/components/bottom_navbar.dart';
|
||||
import 'package:freekake/components/main_menu.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/screen/saya/akun_saya.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'package:image_picker_web/image_picker_web.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
class ProfileScreenCopy extends StatefulWidget {
|
||||
const ProfileScreenCopy({super.key});
|
||||
|
||||
@override
|
||||
_ProfileScreenState createState() => _ProfileScreenState();
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
// import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:freekake/components/bottom_navbar.dart';
|
||||
import 'package:freekake/components/main_menu.dart';
|
||||
import 'package:freekake/components/scan_button.dart';
|
||||
import 'package:freekake/components/topbar_container.dart';
|
||||
import 'package:freekake/screen/saya/akun_saya.dart';
|
||||
import 'package:freekake/screen/saya/data_saya.dart';
|
||||
import 'package:freekake/screen/saya/point_saya.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
|
||||
// import 'package:image_picker_web/image_picker_web.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
class ProfileScreen extends StatefulWidget {
|
||||
const ProfileScreen({super.key});
|
||||
|
||||
@override
|
||||
_ProfileScreenState createState() => _ProfileScreenState();
|
||||
}
|
||||
@@ -23,6 +28,7 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
// final ImagePicker _picker = ImagePicker();
|
||||
dynamic _profileImage;
|
||||
dynamic _headerImage;
|
||||
ImageProvider? imageProvider;
|
||||
final ImagePicker _picker = ImagePicker();
|
||||
|
||||
Future<void> _pickImage(bool isProfile) async {
|
||||
@@ -40,19 +46,28 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
} else {
|
||||
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedFile != null) {
|
||||
final imageFile = File(pickedFile.path);
|
||||
setState(() {
|
||||
if (isProfile) {
|
||||
_profileImage = File(pickedFile.path);
|
||||
_profileImage = imageFile;
|
||||
} else {
|
||||
_headerImage = File(pickedFile.path);
|
||||
_headerImage = imageFile;
|
||||
}
|
||||
|
||||
imageProvider =
|
||||
kIsWeb ? MemoryImage(_profileImage) : FileImage(_profileImage);
|
||||
});
|
||||
} else {
|
||||
imageProvider = AssetImage("assets/images/luffy.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final buttonScanSize = screenWidth * 0.20;
|
||||
final double bottomPadding = (9000 / screenWidth).clamp(0, 0.2);
|
||||
return Scaffold(
|
||||
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
body: Stack(
|
||||
@@ -131,10 +146,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
: null,
|
||||
child:
|
||||
_profileImage == null
|
||||
? Icon(
|
||||
Icons.person,
|
||||
size: 50,
|
||||
color: Colors.white,
|
||||
? Image.asset(
|
||||
"assets/images/luffy.png",
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -158,9 +171,16 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Name",
|
||||
"Luffy",
|
||||
style: TextStyle(
|
||||
color: Colors.blueAccent,
|
||||
color: const Color.fromARGB(
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -168,9 +188,15 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
"Edit Profile",
|
||||
"Luffy Kun",
|
||||
style: TextStyle(
|
||||
color: Colors.blueAccent,
|
||||
color: const Color.fromARGB(
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -191,12 +217,24 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 25.0),
|
||||
child: Divider(height: 0),
|
||||
),
|
||||
_buildListItem(Icons.person, "Data Diri", () => {}),
|
||||
_buildListItem(Icons.person, "Data Diri", () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DataSaya()),
|
||||
);
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 25.0),
|
||||
child: Divider(height: 0),
|
||||
),
|
||||
_buildListItem(Icons.person, "Point Saya", () => {}),
|
||||
_buildListItem(Icons.person, "Point Saya", () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => PointSaya(),
|
||||
),
|
||||
);
|
||||
}),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 25.0,
|
||||
@@ -340,8 +378,8 @@ class _ProfileScreenState extends State<ProfileScreen> {
|
||||
),
|
||||
Positioned(bottom: 0, left: 0, right: 0, child: MainMenu()),
|
||||
Positioned(
|
||||
bottom: 5,
|
||||
left: MediaQuery.of(context).size.width / 2 - 43,
|
||||
bottom: bottomPadding * 98,
|
||||
left: (screenWidth - buttonScanSize) / 2,
|
||||
child: Transform.translate(
|
||||
offset: Offset(0, -20),
|
||||
child: ScanButton(),
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
class ApiClient {
|
||||
final Dio _dio = Dio();
|
||||
final String _baseUrl = 'https://api.freekake.com/';
|
||||
|
||||
ApiClient() {
|
||||
_dio.options.baseUrl = _baseUrl;
|
||||
_dio.options.connectTimeout = const Duration(seconds: 5);
|
||||
_dio.options.receiveTimeout = const Duration(seconds: 3);
|
||||
|
||||
_dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) {
|
||||
print('REQUEST[${options.method}] => PATH: ${options.path}');
|
||||
// options.headers['Authorization'] = 'Bearer your_token_here';
|
||||
return handler.next(options);
|
||||
},
|
||||
onResponse: (response, handler) {
|
||||
print(
|
||||
'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}',
|
||||
);
|
||||
return handler.next(response);
|
||||
},
|
||||
onError: (DioException e, handler) {
|
||||
print(
|
||||
'ERROR[${e.response?.statusCode}] => PATH: ${e.requestOptions.path}',
|
||||
);
|
||||
return handler.next(e);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Dio get dio => _dio;
|
||||
}
|
||||