feat: init project
|
@ -0,0 +1 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
|
@ -0,0 +1,13 @@
|
|||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
|
@ -0,0 +1,47 @@
|
|||
plugins {
|
||||
id "com.android.application"
|
||||
// START: FlutterFire Configuration
|
||||
id 'com.google.gms.google-services'
|
||||
// END: FlutterFire Configuration
|
||||
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.example.forward_chaining_man_app"
|
||||
compileSdk = 34
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.forward_chaining_man_app"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = 23
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
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.debug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
{
|
||||
"project_info": {
|
||||
"project_number": "1072369607324",
|
||||
"project_id": "sirekomendasi-dc7de",
|
||||
"storage_bucket": "sirekomendasi-dc7de.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1072369607324:android:f76a929d6f6258726f4359",
|
||||
"android_client_info": {
|
||||
"package_name": "com.e"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "1072369607324-pnvpbn64ceilqdub09qfj2h6re5199tj.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.e",
|
||||
"certificate_hash": "54c882833621e66da646ffb2d8ee7237fa7f0a24"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "1072369607324-t2qd5p0r7lviqemnvjidshbud49es45l.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.e",
|
||||
"certificate_hash": "3bb526cd960a2c584c325eb17221250f321e34d0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBIHdD_rH3YSfuZYcNKFd1YCMTXxYb6gdc"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.example.forwardChainingManApp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:1072369607324:android:ed7767d8870c32e96f4359",
|
||||
"android_client_info": {
|
||||
"package_name": "com.example.forward_chaining_man_app"
|
||||
}
|
||||
},
|
||||
"oauth_client": [
|
||||
{
|
||||
"client_id": "1072369607324-hp2vujt95ipkft4n348a96ndbiocqrdo.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.example.forward_chaining_man_app",
|
||||
"certificate_hash": "54c882833621e66da646ffb2d8ee7237fa7f0a24"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "1072369607324-pbgftae664k56gli4fb0ftrhot88t8qi.apps.googleusercontent.com",
|
||||
"client_type": 1,
|
||||
"android_info": {
|
||||
"package_name": "com.example.forward_chaining_man_app",
|
||||
"certificate_hash": "3bb526cd960a2c584c325eb17221250f321e34d0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
}
|
||||
],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyBIHdD_rH3YSfuZYcNKFd1YCMTXxYb6gdc"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": [
|
||||
{
|
||||
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||
"client_type": 3
|
||||
},
|
||||
{
|
||||
"client_id": "1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5.apps.googleusercontent.com",
|
||||
"client_type": 2,
|
||||
"ios_info": {
|
||||
"bundle_id": "com.example.forwardChainingManApp"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
|
@ -0,0 +1,49 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:label="Edu Guide"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- 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
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
|
@ -0,0 +1,5 @@
|
|||
package com.example.forward_chaining_man_app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity: FlutterActivity()
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 42 KiB |
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
|
@ -0,0 +1,18 @@
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.buildDir = "../build"
|
||||
subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
|
|
@ -0,0 +1,28 @@
|
|||
pluginManagement {
|
||||
def flutterSdkPath = {
|
||||
def properties = new Properties()
|
||||
file("local.properties").withInputStream { properties.load(it) }
|
||||
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||
return 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.1.0" apply false
|
||||
// START: FlutterFire Configuration
|
||||
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||
// END: FlutterFire Configuration
|
||||
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
|
@ -0,0 +1,279 @@
|
|||
|
||||
[
|
||||
{
|
||||
"NO": "",
|
||||
"PROGRAM": "",
|
||||
"NAMA PROGRAM STUDI": "",
|
||||
"BKT PER SEMESTER": "",
|
||||
"UKT_KELOMPOK_1": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||
"UKT_KELOMPOK_2": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||
"UKT_KELOMPOK_3": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI 75%",
|
||||
"UKT_KELOMPOK_4": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI 50%",
|
||||
"UKT_KELOMPOK_5": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI 25%",
|
||||
"UKT_KELOMPOK_6": "UKT_PENDIDIKAN_UNGGUL"
|
||||
},
|
||||
{
|
||||
"NO": "1",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Perangkat Lunak",
|
||||
"BKT PER SEMESTER": "28.008.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "2",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Mesin",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "3",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Elektro",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "4",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Instrumentasi dan Kontrol",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "5",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Survei dan Pemetaan Dasar",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "6",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Sistem Informasi Geografis",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "7",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Pengembangan Produk Agroindustri",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "8",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknik Pengelolaan dan Perawatan Alat Berat",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "9",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Internet",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "10",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknik Pengelolaan dan Pemeliharaan Infrastruktur Sipil",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "11",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Pelaksanaan Bangunan Sipil",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "12",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Veteriner",
|
||||
"BKT PER SEMESTER": "27.128.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "13",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Manajemen Informasi Kesehatan",
|
||||
"BKT PER SEMESTER": "25.038.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "14",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Pengelolaan Hutan",
|
||||
"BKT PER SEMESTER": "23.995.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.325.000",
|
||||
"UKT_KELOMPOK_4": "6.650.000",
|
||||
"UKT_KELOMPOK_5": "9.975.000",
|
||||
"UKT_KELOMPOK_6": "13.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "15",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Bisnis Perjalanan Wisata",
|
||||
"BKT PER SEMESTER": "23.467.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
},
|
||||
{
|
||||
"NO": "16",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Manajemen dan Penilaian Properti",
|
||||
"BKT PER SEMESTER": "13.826.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
},
|
||||
{
|
||||
"NO": "17",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Pengelolaan Arsip dan Rekaman Informasi",
|
||||
"BKT PER SEMESTER": "13.298.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
},
|
||||
{
|
||||
"NO": "18",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Bahasa Inggris",
|
||||
"BKT PER SEMESTER": "13.298.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
},
|
||||
{
|
||||
"NO": "19",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Bahasa Jepang untuk Komunikasi Bisnis dan Profesional",
|
||||
"BKT PER SEMESTER": "13.298.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
},
|
||||
{
|
||||
"NO": "20",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Akuntansi Sektor Publik",
|
||||
"BKT PER SEMESTER": "13.298.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
},
|
||||
{
|
||||
"NO": "21",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Pembangunan Ekonomi Kewilayahan",
|
||||
"BKT PER SEMESTER": "13.298.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
},
|
||||
{
|
||||
"NO": "22",
|
||||
"PROGRAM": "D4",
|
||||
"NAMA PROGRAM STUDI": "Perbankan",
|
||||
"BKT PER SEMESTER": "13.298.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.850.000",
|
||||
"UKT_KELOMPOK_4": "5.700.000",
|
||||
"UKT_KELOMPOK_5": "8.550.000",
|
||||
"UKT_KELOMPOK_6": "11.400.000"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,866 @@
|
|||
[
|
||||
{
|
||||
"NO": "",
|
||||
"PROGRAM": "",
|
||||
"NAMA PROGRAM STUDI": "",
|
||||
"BKT PER SEMESTER": "",
|
||||
"UKT_KELOMPOK_1": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||
"UKT_KELOMPOK_2": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||
"UKT_KELOMPOK_3": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_75%",
|
||||
"UKT_KELOMPOK_4": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_50%",
|
||||
"UKT_KELOMPOK_5": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_25%",
|
||||
"UKT_KELOMPOK_6": "UKT_PENDIDIKAN_UNGGUL"
|
||||
},
|
||||
{
|
||||
"NO": "1",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Kedokteran",
|
||||
"BKT PER SEMESTER": "35.145.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "6.175.000",
|
||||
"UKT_KELOMPOK_4": "12.350.000",
|
||||
"UKT_KELOMPOK_5": "18.525.000",
|
||||
"UKT_KELOMPOK_6": "24.700.000"
|
||||
},
|
||||
{
|
||||
"NO": "2",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Kedokteran Hewan",
|
||||
"BKT PER SEMESTER": "35.145.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "6.175.000",
|
||||
"UKT_KELOMPOK_4": "12.350.000",
|
||||
"UKT_KELOMPOK_5": "18.525.000",
|
||||
"UKT_KELOMPOK_6": "24.700.000"
|
||||
},
|
||||
{
|
||||
"NO": "3",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Kedokteran Gigi",
|
||||
"BKT PER SEMESTER": "33.740.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "6.175.000",
|
||||
"UKT_KELOMPOK_4": "12.350.000",
|
||||
"UKT_KELOMPOK_5": "18.525.000",
|
||||
"UKT_KELOMPOK_6": "24.700.000"
|
||||
},
|
||||
{
|
||||
"NO": "4",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Farmasi",
|
||||
"BKT PER SEMESTER": "27.919.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "4.325.000",
|
||||
"UKT_KELOMPOK_4": "8.650.000",
|
||||
"UKT_KELOMPOK_5": "12.975.000",
|
||||
"UKT_KELOMPOK_6": "17.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "5",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu Keperawatan",
|
||||
"BKT PER SEMESTER": "26.802.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "4.325.000",
|
||||
"UKT_KELOMPOK_4": "8.650.000",
|
||||
"UKT_KELOMPOK_5": "12.975.000",
|
||||
"UKT_KELOMPOK_6": "17.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "6",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Mesin",
|
||||
"BKT PER SEMESTER": "20.949.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.375.000",
|
||||
"UKT_KELOMPOK_4": "6.750.000",
|
||||
"UKT_KELOMPOK_5": "10.125.000",
|
||||
"UKT_KELOMPOK_6": "13.500.000"
|
||||
},
|
||||
{
|
||||
"NO": "7",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Nuklir",
|
||||
"BKT PER SEMESTER": "20.949.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "8",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Psikologi",
|
||||
"BKT PER SEMESTER": "20.692.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "9",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Geologi",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "10",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Hasil Perikanan",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "11",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Perencanaan Wilayah dan Kota",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "12",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Arsitektur",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "13",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Geodesi",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "14",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Kimia",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.375.000",
|
||||
"UKT_KELOMPOK_4": "6.750.000",
|
||||
"UKT_KELOMPOK_5": "10.125.000",
|
||||
"UKT_KELOMPOK_6": "13.500.000"
|
||||
},
|
||||
{
|
||||
"NO": "15",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Elektro",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "16",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Industri",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "17",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Industri Pertanian",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "18",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Fisika",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "19",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Sipil",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "20",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Elektronika dan Instrumentasi",
|
||||
"BKT PER SEMESTER": "19.908.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "21",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Gizi",
|
||||
"BKT PER SEMESTER": "19.865.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "22",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Biomedis",
|
||||
"BKT PER SEMESTER": "19.273.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "23",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Pertanian",
|
||||
"BKT PER SEMESTER": "19.112.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "24",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Pangan dan Hasil Pertanian",
|
||||
"BKT PER SEMESTER": "19.112.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "25",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Higiene Gigi",
|
||||
"BKT PER SEMESTER": "19.037.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.375.000",
|
||||
"UKT_KELOMPOK_4": "6.750.000",
|
||||
"UKT_KELOMPOK_5": "10.125.000",
|
||||
"UKT_KELOMPOK_6": "13.500.000"
|
||||
},
|
||||
{
|
||||
"NO": "26",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Biologi",
|
||||
"BKT PER SEMESTER": "18.399.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "27",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Kimia",
|
||||
"BKT PER SEMESTER": "18.399.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "28",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Infrastruktur Lingkungan",
|
||||
"BKT PER SEMESTER": "18.315.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "29",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknik Sumber Daya Air",
|
||||
"BKT PER SEMESTER": "18.315.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "30",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Geofisika",
|
||||
"BKT PER SEMESTER": "17.663.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "31",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Fisika",
|
||||
"BKT PER SEMESTER": "17.663.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "32",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Mikrobiologi Pertanian",
|
||||
"BKT PER SEMESTER": "15.335.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "33",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Kehutanan",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "34",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu dan Industri Peternakan",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.750.000",
|
||||
"UKT_KELOMPOK_4": "5.500.000",
|
||||
"UKT_KELOMPOK_5": "8.250.000",
|
||||
"UKT_KELOMPOK_6": "11.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "35",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Agronomi",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "36",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Akuakultur",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "37",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ekonomi Pertanian dan Agribisnis",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "38",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu Tanah",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "39",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Manajemen Sumberdaya Akuatik",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "40",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Penyuluhan dan Komunikasi Pertanian",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "41",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Proteksi Tanaman",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "42",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Teknologi Informasi",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "3.075.000",
|
||||
"UKT_KELOMPOK_4": "6.150.000",
|
||||
"UKT_KELOMPOK_5": "9.225.000",
|
||||
"UKT_KELOMPOK_6": "12.300.000"
|
||||
},
|
||||
{
|
||||
"NO": "43",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Geografi Lingkungan",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "44",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Kartografi dan Penginderaan Jauh",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "45",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Akuntansi",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.300.000",
|
||||
"UKT_KELOMPOK_4": "4.600.000",
|
||||
"UKT_KELOMPOK_5": "6.900.000",
|
||||
"UKT_KELOMPOK_6": "9.200.000"
|
||||
},
|
||||
{
|
||||
"NO": "46",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Manajemen",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.300.000",
|
||||
"UKT_KELOMPOK_4": "4.600.000",
|
||||
"UKT_KELOMPOK_5": "6.900.000",
|
||||
"UKT_KELOMPOK_6": "9.200.000"
|
||||
},
|
||||
{
|
||||
"NO": "47",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Hukum",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.300.000",
|
||||
"UKT_KELOMPOK_4": "4.600.000",
|
||||
"UKT_KELOMPOK_5": "6.900.000",
|
||||
"UKT_KELOMPOK_6": "9.200.000"
|
||||
},
|
||||
{
|
||||
"NO": "48",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu Komputer",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "49",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu Ekonomi",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.300.000",
|
||||
"UKT_KELOMPOK_4": "4.600.000",
|
||||
"UKT_KELOMPOK_5": "6.900.000",
|
||||
"UKT_KELOMPOK_6": "9.200.000"
|
||||
},
|
||||
{
|
||||
"NO": "50",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Manajemen dan Kebijakan Publik",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.300.000",
|
||||
"UKT_KELOMPOK_4": "4.600.000",
|
||||
"UKT_KELOMPOK_5": "6.900.000",
|
||||
"UKT_KELOMPOK_6": "9.200.000"
|
||||
},
|
||||
{
|
||||
"NO": "51",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu Hubungan Internasional",
|
||||
"BKT PER SEMESTER": "14.667.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.200.000",
|
||||
"UKT_KELOMPOK_4": "4.400.000",
|
||||
"UKT_KELOMPOK_5": "6.600.000",
|
||||
"UKT_KELOMPOK_6": "8.800.000"
|
||||
},
|
||||
{
|
||||
"NO": "52",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu Komunikasi",
|
||||
"BKT PER SEMESTER": "14.081.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.300.000",
|
||||
"UKT_KELOMPOK_4": "4.600.000",
|
||||
"UKT_KELOMPOK_5": "6.900.000",
|
||||
"UKT_KELOMPOK_6": "9.200.000"
|
||||
},
|
||||
{
|
||||
"NO": "53",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Pembangunan Sosial dan Kesejahteraan",
|
||||
"BKT PER SEMESTER": "14.081.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.200.000",
|
||||
"UKT_KELOMPOK_4": "4.400.000",
|
||||
"UKT_KELOMPOK_5": "6.600.000",
|
||||
"UKT_KELOMPOK_6": "8.800.000"
|
||||
},
|
||||
{
|
||||
"NO": "54",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Pariwisata",
|
||||
"BKT PER SEMESTER": "14.081.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.900.000",
|
||||
"UKT_KELOMPOK_4": "3.800.000",
|
||||
"UKT_KELOMPOK_5": "5.700.000",
|
||||
"UKT_KELOMPOK_6": "7.600.000"
|
||||
},
|
||||
{
|
||||
"NO": "55",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Statistika",
|
||||
"BKT PER SEMESTER": "13.360.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "56",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Matematika",
|
||||
"BKT PER SEMESTER": "13.360.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "57",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Ilmu Aktuaria",
|
||||
"BKT PER SEMESTER": "12.292.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.500.000",
|
||||
"UKT_KELOMPOK_4": "5.000.000",
|
||||
"UKT_KELOMPOK_5": "7.500.000",
|
||||
"UKT_KELOMPOK_6": "10.000.000"
|
||||
},
|
||||
{
|
||||
"NO": "58",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Arkeologi",
|
||||
"BKT PER SEMESTER": "9.442.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.300.000",
|
||||
"UKT_KELOMPOK_4": "4.600.000",
|
||||
"UKT_KELOMPOK_5": "6.900.000",
|
||||
"UKT_KELOMPOK_6": "9.200.000"
|
||||
},
|
||||
{
|
||||
"NO": "59",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Pembangunan Wilayah",
|
||||
"BKT PER SEMESTER": "9.025.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.256.000",
|
||||
"UKT_KELOMPOK_4": "4.512.000",
|
||||
"UKT_KELOMPOK_5": "6.768.000",
|
||||
"UKT_KELOMPOK_6": "9.025.000"
|
||||
},
|
||||
{
|
||||
"NO": "60",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Bahasa dan Sastra Prancis",
|
||||
"BKT PER SEMESTER": "8.664.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.900.000",
|
||||
"UKT_KELOMPOK_4": "3.800.000",
|
||||
"UKT_KELOMPOK_5": "5.700.000",
|
||||
"UKT_KELOMPOK_6": "7.600.000"
|
||||
},
|
||||
{
|
||||
"NO": "61",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Sastra Arab",
|
||||
"BKT PER SEMESTER": "8.664.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.900.000",
|
||||
"UKT_KELOMPOK_4": "3.800.000",
|
||||
"UKT_KELOMPOK_5": "5.700.000",
|
||||
"UKT_KELOMPOK_6": "7.600.000"
|
||||
},
|
||||
{
|
||||
"NO": "62",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Bahasa dan Sastra Indonesia",
|
||||
"BKT PER SEMESTER": "8.664.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.900.000",
|
||||
"UKT_KELOMPOK_4": "3.800.000",
|
||||
"UKT_KELOMPOK_5": "5.700.000",
|
||||
"UKT_KELOMPOK_6": "7.600.000"
|
||||
},
|
||||
{
|
||||
"NO": "63",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Bahasa dan Kebudayaan Korea",
|
||||
"BKT PER SEMESTER": "8.664.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.900.000",
|
||||
"UKT_KELOMPOK_4": "3.800.000",
|
||||
"UKT_KELOMPOK_5": "5.700.000",
|
||||
"UKT_KELOMPOK_6": "7.600.000"
|
||||
},
|
||||
{
|
||||
"NO": "64",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Bahasa dan Kebudayaan Jepang",
|
||||
"BKT PER SEMESTER": "8.664.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.900.000",
|
||||
"UKT_KELOMPOK_4": "3.800.000",
|
||||
"UKT_KELOMPOK_5": "5.700.000",
|
||||
"UKT_KELOMPOK_6": "7.600.000"
|
||||
},
|
||||
{
|
||||
"NO": "65",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Bahasa, Sastra, dan Budaya Jawa",
|
||||
"BKT PER SEMESTER": "8.664.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.900.000",
|
||||
"UKT_KELOMPOK_4": "3.800.000",
|
||||
"UKT_KELOMPOK_5": "5.700.000",
|
||||
"UKT_KELOMPOK_6": "7.600.000"
|
||||
},
|
||||
{
|
||||
"NO": "66",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Sastra Inggris",
|
||||
"BKT PER SEMESTER": "8.664.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.200.000",
|
||||
"UKT_KELOMPOK_4": "4.400.000",
|
||||
"UKT_KELOMPOK_5": "6.600.000",
|
||||
"UKT_KELOMPOK_6": "8.664.000"
|
||||
},
|
||||
{
|
||||
"NO": "67",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Politik dan Pemerintahan",
|
||||
"BKT PER SEMESTER": "8.214.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.053.000",
|
||||
"UKT_KELOMPOK_4": "4.107.000",
|
||||
"UKT_KELOMPOK_5": "6.160.000",
|
||||
"UKT_KELOMPOK_6": "8.214.000"
|
||||
},
|
||||
{
|
||||
"NO": "68",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Antropologi Budaya",
|
||||
"BKT PER SEMESTER": "7.885.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.053.000",
|
||||
"UKT_KELOMPOK_4": "4.107.000",
|
||||
"UKT_KELOMPOK_5": "6.160.000",
|
||||
"UKT_KELOMPOK_6": "7.885.000"
|
||||
},
|
||||
{
|
||||
"NO": "69",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Sejarah",
|
||||
"BKT PER SEMESTER": "7.885.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "2.053.000",
|
||||
"UKT_KELOMPOK_4": "4.107.000",
|
||||
"UKT_KELOMPOK_5": "6.160.000",
|
||||
"UKT_KELOMPOK_6": "7.885.000"
|
||||
},
|
||||
{
|
||||
"NO": "70",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Sosiologi",
|
||||
"BKT PER SEMESTER": "7.885.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.889.000",
|
||||
"UKT_KELOMPOK_4": "3.778.000",
|
||||
"UKT_KELOMPOK_5": "5.667.000",
|
||||
"UKT_KELOMPOK_6": "7.557.000"
|
||||
},
|
||||
{
|
||||
"NO": "71",
|
||||
"PROGRAM": "S1",
|
||||
"NAMA PROGRAM STUDI": "Filsafat",
|
||||
"BKT PER SEMESTER": "7.885.000",
|
||||
"UKT_KELOMPOK_1": "0",
|
||||
"UKT_KELOMPOK_2": "0",
|
||||
"UKT_KELOMPOK_3": "1.889.000",
|
||||
"UKT_KELOMPOK_4": "3.778.000",
|
||||
"UKT_KELOMPOK_5": "5.667.000",
|
||||
"UKT_KELOMPOK_6": "7.557.000"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
"beasiswa": [
|
||||
{
|
||||
"nama": "Beasiswa Indonesia Maju (BIM)",
|
||||
"penyelenggara": "Kementerian Pendidikan, Kebudayaan, Riset, dan Teknologi",
|
||||
"jenjang": ["S1", "S2"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Beasiswa bergelar (S1 dan S2) serta beasiswa non-gelar (Program Persiapan S1 Luar Negeri).",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0",
|
||||
"Lolos seleksi administrasi, substansi, dan wawancara"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://bim-pusatprestasinasional.kemdikbud.go.id/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa KIP Kuliah",
|
||||
"penyelenggara": "Kementerian Pendidikan, Kebudayaan, Riset, dan Teknologi",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Bantuan biaya pendidikan dan biaya hidup bagi mahasiswa dari keluarga kurang mampu.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki KIP",
|
||||
"Lolos seleksi administrasi"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://kip-kuliah.kemdikbud.go.id/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa LPDP (Lembaga Pengelola Dana Pendidikan)",
|
||||
"penyelenggara": "Kementerian Keuangan RI",
|
||||
"jenjang": ["S1", "S2", "S3"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Beasiswa penuh yang mencakup biaya pendidikan dan biaya hidup untuk pendidikan dalam dan luar negeri.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0 untuk S1, 3.25 untuk S2/S3",
|
||||
"Usia maksimal 21 tahun untuk S1, 35 tahun untuk S2, 40 tahun untuk S3",
|
||||
"Lolos seleksi administrasi, substansi, dan wawancara"
|
||||
],
|
||||
"deadline": "Biasanya dibuka 2-3 kali setahun",
|
||||
"link": "https://www.lpdp.kemenkeu.go.id"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa S1 Glow and Lovely Bintang Beasiswa",
|
||||
"penyelenggara": "Glow and Lovely",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Beasiswa penuh untuk program S1 di bidang yang relevan.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki prestasi akademik yang baik"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://www.glowandlovely.in"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Bank Indonesia",
|
||||
"penyelenggara": "Bank Indonesia",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Ekonomi", "Keuangan", "Pendidikan"],
|
||||
"deskripsi": "Bantuan dana pendidikan bulanan, pelatihan soft skill dan hard skill, serta workshop.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0",
|
||||
"Lolos seleksi administrasi dan wawancara"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://www.kompas.com/edu/read/2025/01/27/125955371/beasiswa-bank-indonesia-2025-dibuka-cek-syarat-dan-cara-daftar"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Van Deventer-Maas Indonesia (VDMI)",
|
||||
"penyelenggara": "VDMI",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Bantuan dana bulanan, bonus lulus tepat waktu, bonus TOEFL, serta pelatihan pengembangan kapasitas pribadi.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki prestasi akademik yang baik",
|
||||
"Lolos seleksi administrasi dan wawancara"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://beasiswapascasarjana.com"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Dataprint",
|
||||
"penyelenggara": "Dataprint",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Bantuan dana sebesar Rp500.000 yang diberikan satu kali.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki prestasi akademik yang baik"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://indbeasiswa.com/beasiswa-dataprint/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Pertamina Sobat Bumi",
|
||||
"penyelenggara": "Pertamina",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Dana hidup bulanan dan dana program pengabdian untuk desa.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0",
|
||||
"Lolos seleksi administrasi"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://indbeasiswa.com/beasiswa-pertamina-sobat-bumi/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Karya Salemba Empat (KSE)",
|
||||
"penyelenggara": "Karya Salemba Empat",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Bantuan dana pendidikan dan pengembangan diri.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0",
|
||||
"Lolos seleksi administrasi"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://indbeasiswa.com/beasiswa-karya-salemba-empat/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Djarum Plus",
|
||||
"penyelenggara": "Djarum Foundation",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Bantuan dana bulanan dan program pengembangan diri.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki prestasi akademik yang baik"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://indbeasiswa.com/beasiswa-djarum-plus/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Tanoto Foundation",
|
||||
"penyelenggara": "Tanoto Foundation",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Semua Bidang Studi"],
|
||||
"deskripsi": "Bantuan biaya pendidikan dan biaya hidup.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0",
|
||||
"Lolos seleksi administrasi dan wawancara"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://indbeasiswa.com/beasiswa-tanoto-foundation/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa Bakti BCA",
|
||||
"penyelenggara": "BCA",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Ekonomi", "Pendidikan", "Keuangan"],
|
||||
"deskripsi": "Uang saku bulanan dan bantuan UKT (Uang Kuliah Tunggal).",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0",
|
||||
"Lolos seleksi administrasi dan wawancara"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://indbeasiswa.com/beasiswa-bakti-bca/"
|
||||
},
|
||||
{
|
||||
"nama": "Beasiswa BCA Finance",
|
||||
"penyelenggara": "BCA Finance",
|
||||
"jenjang": ["S1"],
|
||||
"bidang_studi": ["Ekonomi", "Keuangan"],
|
||||
"deskripsi": "Bantuan biaya kuliah per semester.",
|
||||
"persyaratan": [
|
||||
"WNI",
|
||||
"Memiliki IPK minimal 3.0"
|
||||
],
|
||||
"deadline": "Biasanya dibuka setiap tahun",
|
||||
"link": "https://indbeasiswa.com/beasiswa-bca-finance/"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"data_ekonomi": [
|
||||
{
|
||||
"kode": "E01",
|
||||
"deskripsi": "Saya dan keluarga memiliki kondisi ekonomi yang cukup untuk melanjutkan kuliah"
|
||||
},
|
||||
{
|
||||
"kode": "E02",
|
||||
"deskripsi": "Saya perlu mempertimbangkan biaya karena kondisi ekonomi keluarga terbatas"
|
||||
},
|
||||
{
|
||||
"kode": "E03",
|
||||
"deskripsi": "Saya berminat kuliah tapi berencana mencari beasiswa atau bantuan pendanaan"
|
||||
},
|
||||
{
|
||||
"kode": "E04",
|
||||
"deskripsi": "Saya lebih memilih bekerja atau usaha karena kondisi ekonomi saat ini"
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 6.8 KiB |
|
@ -0,0 +1,734 @@
|
|||
{
|
||||
"J01A-Kerja": {
|
||||
"name": "IPA (Sains Murni) - Kerja",
|
||||
"description": "Fokus: Biologi, Kimia, Fisika (pekerjaan di bidang kesehatan, sains, farmasi, laboratorium, lingkungan) tanpa kuliah formal, bisa belajar mandiri melalui kursus, sertifikasi, dan pelatihan.",
|
||||
"categories": [
|
||||
"Kesehatan & Sains",
|
||||
"Penelitian",
|
||||
"Pekerjaan Sains",
|
||||
"Teknis Laboratorium"
|
||||
],
|
||||
"minat": {
|
||||
"Kedokteran": {
|
||||
"pertanyaan": [
|
||||
"AKER01: Saya tertarik bekerja di rumah sakit atau klinik tanpa kuliah formal [6]",
|
||||
"AKER02: Saya tertarik ingin mempelajari cara dasar merawat pasien atau membantu mereka sembuh [7]",
|
||||
"AKER03: Saya tertarik untuk mendapatkan pengetahuan medis melalui kursus online atau pelatihan [6]",
|
||||
"AKER04: Saya tertarik ingin bekerja sebagai asisten medis atau tenaga medis tanpa harus menjadi dokter [5]",
|
||||
"AKER05: Saya tertarik dengan pekerjaan administrasi di lingkungan rumah sakit atau klinik [5]",
|
||||
"AKER06: Saya tertarik memiliki empati tinggi dan ingin membantu pasien secara langsung [5]"
|
||||
],
|
||||
"karir": [
|
||||
"AKAR01: Asisten Medis [20]",
|
||||
"AKAR02: Tenaga Kesehatan [20]",
|
||||
"AKAR03: Staf Klinik [20]",
|
||||
"AKAR04: Administrasi Rumah Sakit [20]",
|
||||
"AKAR05: Teknisi Alat Kesehatan [20]",
|
||||
"AKAR06: Customer Service Asuransi Kesehatan [20]"
|
||||
],
|
||||
"jurusan_terkait": [
|
||||
"D3 Keperawatan",
|
||||
"D3 Teknologi Laboratorium Medik",
|
||||
"D3 Farmasi",
|
||||
"D3 Rekam Medis dan Informasi Kesehatan",
|
||||
"D3 Teknologi Bank Darah",
|
||||
"S1 Kesehatan Masyarakat"
|
||||
],
|
||||
"rekomendasi_kursus": [
|
||||
"Pelatihan Bantuan Hidup Dasar (BHD)",
|
||||
"Kursus Asisten Perawat Tersertifikasi",
|
||||
"Pelatihan Administrasi Rumah Sakit",
|
||||
"Sertifikasi Teknisi Alat Kesehatan"
|
||||
]
|
||||
},
|
||||
"Sains": {
|
||||
"pertanyaan": [
|
||||
"AKER07: Saya tertarik ingin menjadi peneliti atau bekerja di laboratorium [7]",
|
||||
"AKER08: Saya tertarik senang melakukan eksperimen ilmiah dan mempelajari data percobaan [6]",
|
||||
"AKER09: Saya tertarik untuk bekerja di bidang riset atau pengembangan produk tanpa kuliah formal [6]",
|
||||
"AKER10: Saya tertarik ingin bekerja dengan tim sains di industri atau lembaga penelitian [6]",
|
||||
"AKER11: Saya tertarik teliti dan mampu mengikuti protokol dengan tepat [5]",
|
||||
"AKER12: Saya tertarik dalam pengolahan dan analisis data penelitian [5]"
|
||||
],
|
||||
"karir": [
|
||||
"AKAR07: Teknisi Laboratorium [20]",
|
||||
"AKAR08: Teknisi Peralatan Sains [20]",
|
||||
"AKAR09: Staf R&D [20]",
|
||||
"AKAR10: Pengumpul Data Lapangan [20]",
|
||||
"AKAR11: Operator Peralatan Penelitian [20]",
|
||||
"AKAR12: Staf Pengujian Kualitas [20]"
|
||||
],
|
||||
"jurusan_terkait": [
|
||||
"D3 Analis Kimia",
|
||||
"D3 Teknik Kimia",
|
||||
"S1 Kimia",
|
||||
"S1 Biologi",
|
||||
"S1 Fisika",
|
||||
"D3 MIPA"
|
||||
],
|
||||
"rekomendasi_kursus": [
|
||||
"Sertifikasi Teknisi Laboratorium",
|
||||
"Pelatihan Good Laboratory Practice (GLP)",
|
||||
"Kursus Pengoperasian Alat Laboratorium",
|
||||
"Pelatihan Analisis Data Dasar",
|
||||
"Sertifikasi K3 Laboratorium"
|
||||
]
|
||||
},
|
||||
"Farmasi": {
|
||||
"pertanyaan": [
|
||||
"AKER13: Saya tertarik untuk bekerja di apotek atau di industri farmasi tanpa kuliah formal [6]",
|
||||
"AKER14: Saya tertarik ingin mempelajari bagaimana obat bekerja melalui kursus atau pelatihan singkat [6]",
|
||||
"AKER15: Saya tertarik dengan pengujian obat dan kualitas farmasi di industri [6]",
|
||||
"AKER16: Saya tertarik ingin menjadi bagian dari tim yang mengembangkan produk obat [6]",
|
||||
"AKER17: Saya tertarik teliti dalam mengukur dan mencatat informasi penting [6]",
|
||||
"AKER18: Saya tertarik dalam penyimpanan dan pendistribusian obat-obatan [6]"
|
||||
],
|
||||
"riasecType": [
|
||||
{
|
||||
"type": [
|
||||
"R",
|
||||
"C"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I",
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I",
|
||||
"S"
|
||||
],
|
||||
"bobot": [
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"C"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"C",
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"karir": [
|
||||
"AKAR13: Asisten Apoteker [20]",
|
||||
"AKAR14: Teknisi Farmasi [20]",
|
||||
"AKAR15: Staf Produksi Obat [20]",
|
||||
"AKAR16: Pengujian Kualitas Obat [20]",
|
||||
"AKAR17: Staf Logistik Farmasi [20]",
|
||||
"AKAR18: Sales Representative Produk Farmasi [20]",
|
||||
"AKAR19: Staf Administrasi Apotek [20]"
|
||||
],
|
||||
"karir_riasec": [
|
||||
"I/C",
|
||||
"I/R",
|
||||
"R/C",
|
||||
"I/C",
|
||||
"C",
|
||||
"E/S",
|
||||
"C"
|
||||
],
|
||||
"jurusan_terkait": [
|
||||
"D3 Farmasi",
|
||||
"S1 Farmasi",
|
||||
"D3 Teknik Kimia",
|
||||
"D3 Analis Kimia",
|
||||
"S1 Kimia Farmasi"
|
||||
],
|
||||
"rekomendasi_kursus": [
|
||||
"Pelatihan Asisten Apoteker",
|
||||
"Sertifikasi Good Manufacturing Practice (GMP)",
|
||||
"Kursus Pengelolaan Obat",
|
||||
"Pelatihan Teknik Penjualan Produk Farmasi",
|
||||
"Kursus Komputer untuk Administrasi Apotek"
|
||||
]
|
||||
},
|
||||
"Laboratorium": {
|
||||
"pertanyaan": [
|
||||
"AKER19: Saya tertarik bekerja dengan peralatan dan instrumen laboratorium [6]",
|
||||
"AKER20: Saya tertarik menyukai pekerjaan yang membutuhkan ketelitian dan konsistensi tinggi [6]",
|
||||
"AKER21: Saya tertarik ingin bekerja di laboratorium klinik, industri, atau penelitian [6]",
|
||||
"AKER22: Saya tertarik untuk mempelajari teknik-teknik analisis laboratorium [6]",
|
||||
"AKER23: Saya tertarik mampu mengikuti protokol dan prosedur standar dengan tepat [6]",
|
||||
"AKER24: Saya tertarik dalam persiapan sampel dan pengujian bahan [6]"
|
||||
],
|
||||
"riasecType": [
|
||||
{
|
||||
"type": [
|
||||
"R",
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"C"
|
||||
],
|
||||
"bobot": [
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"C"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I",
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"karir": [
|
||||
"AKAR20: Teknisi Laboratorium Klinik [20]",
|
||||
"AKAR21: Teknisi Laboratorium Industri [20]",
|
||||
"AKAR22: Staf Pengujian Kualitas [20]",
|
||||
"AKAR23: Teknisi Instrumentasi [20]",
|
||||
"AKAR24: Staf Pengambilan Sampel [20]"
|
||||
],
|
||||
"karir_riasec": [
|
||||
"I/R",
|
||||
"I/R",
|
||||
"I/C",
|
||||
"R/I",
|
||||
"R"
|
||||
],
|
||||
"jurusan_terkait": [
|
||||
"D3 Teknologi Laboratorium Medik",
|
||||
"D3 Analis Kimia",
|
||||
"D3 Analis Kesehatan",
|
||||
"S1 Kimia",
|
||||
"S1 Biologi",
|
||||
"D3 Mikrobiologi"
|
||||
],
|
||||
"rekomendasi_kursus": [
|
||||
"Sertifikasi Teknisi Laboratorium",
|
||||
"Pelatihan Teknik Dasar Mikrobiologi",
|
||||
"Kursus Instrumentasi Laboratorium",
|
||||
"Pelatihan Sistem Manajemen Mutu Laboratorium",
|
||||
"Kursus Pengambilan dan Penanganan Sampel"
|
||||
]
|
||||
},
|
||||
"Lingkungan": {
|
||||
"pertanyaan": [
|
||||
"AKER25: Saya tertarik untuk bekerja dalam pemantauan dan pengelolaan lingkungan [6]",
|
||||
"AKER26: Saya tertarik peduli terhadap isu-isu lingkungan dan pelestarian alam [7]",
|
||||
"AKER27: Saya tertarik suka bekerja di lapangan dan mengumpulkan data lingkungan [7]",
|
||||
"AKER28: Saya tertarik untuk mempelajari cara mengukur polusi dan kualitas lingkungan [6]",
|
||||
"AKER29: Saya tertarik ingin bekerja dalam proyek konservasi atau pengelolaan limbah [6]",
|
||||
"AKER30: Saya tertarik dalam pendidikan lingkungan dan kampanye kesadaran [7]"
|
||||
],
|
||||
"riasecType": [
|
||||
{
|
||||
"type": [
|
||||
"I",
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"S",
|
||||
"A"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"R",
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"R",
|
||||
"S"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"S",
|
||||
"A"
|
||||
],
|
||||
"bobot": [
|
||||
2,
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"karir": [
|
||||
"AKAR25: Teknisi Pemantauan Lingkungan [20]",
|
||||
"AKAR26: Staf Pengelolaan Limbah [20]",
|
||||
"AKAR27: Asisten Pengambil Sampel Lingkungan [20]",
|
||||
"AKAR28: Operator Pengolahan Air [20]",
|
||||
"AKAR29: Staf Lapangan Konservasi [20]",
|
||||
"AKAR30: Teknisi Pengendalian Polusi [20]"
|
||||
],
|
||||
"karir_riasec": [
|
||||
"I/R",
|
||||
"R/C",
|
||||
"R/I",
|
||||
"R",
|
||||
"R/S",
|
||||
"I/R"
|
||||
],
|
||||
"jurusan_terkait": [
|
||||
"D3 Teknik Lingkungan",
|
||||
"S1 Teknik Lingkungan",
|
||||
"S1 Ilmu Lingkungan",
|
||||
"S1 Biologi Lingkungan",
|
||||
"D3 Pengelolaan Sumber Daya Air dan Lingkungan"
|
||||
],
|
||||
"rekomendasi_kursus": [
|
||||
"Sertifikasi Teknisi Lingkungan",
|
||||
"Pelatihan Pengelolaan Limbah",
|
||||
"Kursus Pengambilan Sampel Lingkungan",
|
||||
"Pelatihan Operator Instalasi Pengolahan Air",
|
||||
"Kursus K3 Lingkungan"
|
||||
]
|
||||
},
|
||||
"Pangan dan Nutrisi": {
|
||||
"pertanyaan": [
|
||||
"AKER31: Saya tertarik bekerja dalam pengujian dan pengembangan produk pangan [6]",
|
||||
"AKER32: Saya tertarik ingin mempelajari cara menganalisis kualitas dan keamanan makanan [7]",
|
||||
"AKER33: Saya tertarik untuk bekerja di industri pengolahan pangan [5]",
|
||||
"AKER34: Saya tertarik peduli tentang nutrisi dan dampaknya terhadap kesehatan [5]",
|
||||
"AKER35: Saya tertarik dalam produksi dan pengawasan makanan [5]",
|
||||
"AKER36: Saya tertarik ingin membantu orang dengan masalah nutrisi dan diet [4]"
|
||||
],
|
||||
"riasecType": [
|
||||
{
|
||||
"type": [
|
||||
"I",
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"I",
|
||||
"S"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"R",
|
||||
"C"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"S"
|
||||
],
|
||||
"bobot": [
|
||||
4
|
||||
]
|
||||
}
|
||||
],
|
||||
"karir": [
|
||||
"AKAR31: Teknisi Pengujian Pangan [20]",
|
||||
"AKAR32: Asisten Quality Control Makanan [20]",
|
||||
"AKAR33: Staf Produksi Industri Pangan [20]",
|
||||
"AKAR34: Teknisi Pengolahan Makanan [20]",
|
||||
"AKAR35: Staf Pengembangan Produk Pangan [20]",
|
||||
"AKAR36: Asisten Food Safety [20]"
|
||||
],
|
||||
"karir_riasec": [
|
||||
"I/R",
|
||||
"C/I",
|
||||
"R",
|
||||
"R/I",
|
||||
"I/R",
|
||||
"C/I"
|
||||
],
|
||||
"jurusan_terkait": [
|
||||
"D3 Teknologi Pangan",
|
||||
"S1 Ilmu dan Teknologi Pangan",
|
||||
"D3 Gizi",
|
||||
"S1 Ilmu Gizi",
|
||||
"D3 Pengawasan Mutu Pangan"
|
||||
],
|
||||
"rekomendasi_kursus": [
|
||||
"Sertifikasi Keamanan Pangan (HACCP)",
|
||||
"Pelatihan Quality Control Makanan",
|
||||
"Kursus Pengujian Nutrisi Pangan",
|
||||
"Pelatihan Good Manufacturing Practice (GMP) untuk Pangan",
|
||||
"Kursus Dasar Nutrisi dan Diet"
|
||||
]
|
||||
},
|
||||
"Peralatan Medis": {
|
||||
"pertanyaan": [
|
||||
"AKER37: Saya tertarik bekerja dengan peralatan dan teknologi medis [6]",
|
||||
"AKER38: Saya tertarik ingin mempelajari cara mengoperasikan dan memelihara alat medis [7]",
|
||||
"AKER39: Saya tertarik untuk bekerja di rumah sakit atau perusahaan peralatan medis [5]",
|
||||
"AKER40: Saya tertarik memiliki keterampilan teknis dan ingin menerapkannya di bidang kesehatan [6]",
|
||||
"AKER41: Saya tertarik dalam pengenalan dan pelatihan penggunaan peralatan medis [5]",
|
||||
"AKER42: Saya tertarik suka memperbaiki dan memecahkan masalah pada perangkat elektronik [6]"
|
||||
],
|
||||
"riasecType": [
|
||||
{
|
||||
"type": [
|
||||
"R",
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"S",
|
||||
"E"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"R",
|
||||
"I"
|
||||
],
|
||||
"bobot": [
|
||||
4,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"S",
|
||||
"E"
|
||||
],
|
||||
"bobot": [
|
||||
3,
|
||||
2
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"R"
|
||||
],
|
||||
"bobot": [
|
||||
5
|
||||
]
|
||||
}
|
||||
],
|
||||
"karir": [
|
||||
"AKAR37: Teknisi Peralatan Medis [20]",
|
||||
"AKAR38: Teknisi Laboratorium Medis [20]",
|
||||
"AKAR39: Staf Penjualan Alat Kesehatan [20]",
|
||||
"AKAR40: Teknisi Pemeliharaan Alat Medis [20]",
|
||||
"AKAR41: Staf Logistik Peralatan Medis [20]"
|
||||
],
|
||||
"karir_riasec": [
|
||||
"R/I",
|
||||
"I/R",
|
||||
"E/S",
|
||||
"R",
|
||||
"C/R"
|
||||
],
|
||||
"jurusan_terkait": [
|
||||
"D3 Teknik Elektromedik",
|
||||
"D3 Teknik Elektronika",
|
||||
"D3 Teknik Biomedis",
|
||||
"S1 Teknik Biomedis",
|
||||
"D3 Teknik Instrumentasi"
|
||||
],
|
||||
"rekomendasi_kursus": [
|
||||
"Sertifikasi Teknisi Peralatan Medis",
|
||||
"Pelatihan Dasar Radiologi",
|
||||
"Kursus Elektrokardiografi",
|
||||
"Pelatihan Pemeliharaan Peralatan Laboratorium",
|
||||
"Kursus Penjualan Alat Kesehatan"
|
||||
]
|
||||
}
|
||||
},
|
||||
"semua_pertanyaan": [
|
||||
{
|
||||
"kode_pertanyaan": "AKER01",
|
||||
"isi_pertanyaan": "Saya tertarik bekerja di rumah sakit atau klinik tanpa kuliah formal"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER02",
|
||||
"isi_pertanyaan": "Saya tertarik ingin mempelajari cara dasar merawat pasien atau membantu mereka sembuh"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER03",
|
||||
"isi_pertanyaan": "Saya tertarik untuk mendapatkan pengetahuan medis melalui kursus online atau pelatihan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER04",
|
||||
"isi_pertanyaan": "Saya tertarik ingin bekerja sebagai asisten medis atau tenaga medis tanpa harus menjadi dokter"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER05",
|
||||
"isi_pertanyaan": "Saya tertarik dengan pekerjaan administrasi di lingkungan rumah sakit atau klinik"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER06",
|
||||
"isi_pertanyaan": "Saya tertarik memiliki empati tinggi dan ingin membantu pasien secara langsung"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER07",
|
||||
"isi_pertanyaan": "Saya tertarik ingin menjadi peneliti atau bekerja di laboratorium"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER08",
|
||||
"isi_pertanyaan": "Saya tertarik senang melakukan eksperimen ilmiah dan mempelajari data percobaan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER09",
|
||||
"isi_pertanyaan": "Saya tertarik untuk bekerja di bidang riset atau pengembangan produk tanpa kuliah formal"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER10",
|
||||
"isi_pertanyaan": "Saya tertarik ingin bekerja dengan tim sains di industri atau lembaga penelitian"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER11",
|
||||
"isi_pertanyaan": "Saya tertarik teliti dan mampu mengikuti protokol dengan tepat"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER12",
|
||||
"isi_pertanyaan": "Saya tertarik dalam pengolahan dan analisis data penelitian"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER13",
|
||||
"isi_pertanyaan": "Saya tertarik untuk bekerja di apotek atau di industri farmasi tanpa kuliah formal"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER14",
|
||||
"isi_pertanyaan": "Saya tertarik ingin mempelajari bagaimana obat bekerja melalui kursus atau pelatihan singkat"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER15",
|
||||
"isi_pertanyaan": "Saya tertarik dengan pengujian obat dan kualitas farmasi di industri"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER16",
|
||||
"isi_pertanyaan": "Saya tertarik ingin menjadi bagian dari tim yang mengembangkan produk obat"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER17",
|
||||
"isi_pertanyaan": "Saya tertarik teliti dalam mengukur dan mencatat informasi penting"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER18",
|
||||
"isi_pertanyaan": "Saya tertarik dalam penyimpanan dan pendistribusian obat-obatan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER19",
|
||||
"isi_pertanyaan": "Saya tertarik bekerja dengan peralatan dan instrumen laboratorium"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER20",
|
||||
"isi_pertanyaan": "Saya tertarik menyukai pekerjaan yang membutuhkan ketelitian dan konsistensi tinggi"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER21",
|
||||
"isi_pertanyaan": "Saya tertarik ingin bekerja di laboratorium klinik, industri, atau penelitian"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER22",
|
||||
"isi_pertanyaan": "Saya tertarik untuk mempelajari teknik-teknik analisis laboratorium"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER23",
|
||||
"isi_pertanyaan": "Saya tertarik mampu mengikuti protokol dan prosedur standar dengan tepat"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER24",
|
||||
"isi_pertanyaan": "Saya tertarik dalam persiapan sampel dan pengujian bahan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER25",
|
||||
"isi_pertanyaan": "Saya tertarik untuk bekerja dalam pemantauan dan pengelolaan lingkungan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER26",
|
||||
"isi_pertanyaan": "Saya tertarik peduli terhadap isu-isu lingkungan dan pelestarian alam"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER27",
|
||||
"isi_pertanyaan": "Saya tertarik suka bekerja di lapangan dan mengumpulkan data lingkungan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER28",
|
||||
"isi_pertanyaan": "Saya tertarik untuk mempelajari cara mengukur polusi dan kualitas lingkungan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER29",
|
||||
"isi_pertanyaan": "Saya tertarik ingin bekerja dalam proyek konservasi atau pengelolaan limbah"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER30",
|
||||
"isi_pertanyaan": "Saya tertarik dalam pendidikan lingkungan dan kampanye kesadaran"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER31",
|
||||
"isi_pertanyaan": "Saya tertarik bekerja dalam pengujian dan pengembangan produk pangan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER32",
|
||||
"isi_pertanyaan": "Saya tertarik ingin mempelajari cara menganalisis kualitas dan keamanan makanan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER33",
|
||||
"isi_pertanyaan": "Saya tertarik untuk bekerja di industri pengolahan pangan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER34",
|
||||
"isi_pertanyaan": "Saya tertarik peduli tentang nutrisi dan dampaknya terhadap kesehatan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER35",
|
||||
"isi_pertanyaan": "Saya tertarik dalam produksi dan pengawasan makanan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER36",
|
||||
"isi_pertanyaan": "Saya tertarik ingin membantu orang dengan masalah nutrisi dan diet"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER37",
|
||||
"isi_pertanyaan": "Saya tertarik bekerja dengan peralatan dan teknologi medis"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER38",
|
||||
"isi_pertanyaan": "Saya tertarik ingin mempelajari cara mengoperasikan dan memelihara alat medis"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER39",
|
||||
"isi_pertanyaan": "Saya tertarik untuk bekerja di rumah sakit atau perusahaan peralatan medis"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER40",
|
||||
"isi_pertanyaan": "Saya tertarik memiliki keterampilan teknis dan ingin menerapkannya di bidang kesehatan"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER41",
|
||||
"isi_pertanyaan": "Saya tertarik dalam pengenalan dan pelatihan penggunaan peralatan medis"
|
||||
},
|
||||
{
|
||||
"kode_pertanyaan": "AKER42",
|
||||
"isi_pertanyaan": "Saya tertarik suka memperbaiki dan memecahkan masalah pada perangkat elektronik"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 6.1 MiB |
|
@ -0,0 +1,3 @@
|
|||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
|
@ -0,0 +1 @@
|
|||
{"flutter":{"platforms":{"android":{"default":{"projectId":"sirekomendasi-dc7de","appId":"1:1072369607324:android:f76a929d6f6258726f4359","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"sirekomendasi-dc7de","configurations":{"android":"1:1072369607324:android:f76a929d6f6258726f4359","ios":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","macos":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","web":"1:1072369607324:web:d17906f3452e68cb6f4359","windows":"1:1072369607324:web:7c9de102a40074a66f4359"}}},"ios":{"default":{"projectId":"sirekomendasi-dc7de","appId":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"sirekomendasi-dc7de","appId":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}}}}}
|
|
@ -0,0 +1,34 @@
|
|||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||
#include "Generated.xcconfig"
|
|
@ -0,0 +1,2 @@
|
|||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||
#include "Generated.xcconfig"
|
|
@ -0,0 +1,59 @@
|
|||
# Uncomment this line to define a global platform for your project
|
||||
platform :ios, '13.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
project 'Runner', {
|
||||
'Debug' => :debug,
|
||||
'Profile' => :release,
|
||||
'Release' => :release,
|
||||
}
|
||||
|
||||
def flutter_root
|
||||
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||
unless File.exist?(generated_xcode_build_settings_path)
|
||||
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||
end
|
||||
|
||||
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||
return matches[1].strip if matches
|
||||
end
|
||||
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||
end
|
||||
|
||||
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||
|
||||
flutter_ios_podfile_setup
|
||||
|
||||
target 'Runner' do
|
||||
use_frameworks!
|
||||
use_modular_headers!
|
||||
|
||||
pod 'Firebase/Analytics', :modular_headers => true
|
||||
pod 'Firebase/Auth', :modular_headers => true
|
||||
pod 'Firebase/Core', :modular_headers => true
|
||||
pod 'Firebase/Firestore', :modular_headers => true
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
|
||||
target 'RunnerTests' do
|
||||
inherit! :search_paths
|
||||
end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
flutter_additional_ios_build_settings(target)
|
||||
if target.name == 'BoringSSL-GRPC'
|
||||
target.source_build_phase.files.each do |file|
|
||||
if file.settings && file.settings['COMPILER_FLAGS']
|
||||
flags = file.settings['COMPILER_FLAGS'].split
|
||||
flags.reject! { |flag| flag == '-GCC_WARN_INHIBIT_ALL_WARNINGS' }
|
||||
file.settings['COMPILER_FLAGS'] = flags.join(' ')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,753 @@
|
|||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0B08F4E3191C1718CE721DEE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44309CC712138A6C295BDD13 /* Pods_Runner.framework */; };
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
41BE7AE37D217557707F77F4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A61884E6070EB92C9DA7565 /* Pods_RunnerTests.framework */; };
|
||||
619C01BB6DEEA093B9F64580 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 71E56F6CBCC9735A933C8324 /* GoogleService-Info.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||
remoteInfo = Runner;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
090C37EAACC6628C1878F26D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
3001B99FAF58D3847964C671 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3A61884E6070EB92C9DA7565 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
40E1C4F703AEAFB3F4E96C40 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
44309CC712138A6C295BDD13 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
6623077AAAEF559F09EAAEC6 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
71E56F6CBCC9735A933C8324 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
A8A46054CD2BDDAEAD8EBD4C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
E4D15238BF2B8882453091C1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
822140EDA9A65D30FEC797F5 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
41BE7AE37D217557707F77F4 /* Pods_RunnerTests.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0B08F4E3191C1718CE721DEE /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||
);
|
||||
path = RunnerTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
767DA86CBE420F5563DA86EC /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
A8A46054CD2BDDAEAD8EBD4C /* Pods-Runner.debug.xcconfig */,
|
||||
3001B99FAF58D3847964C671 /* Pods-Runner.release.xcconfig */,
|
||||
40E1C4F703AEAFB3F4E96C40 /* Pods-Runner.profile.xcconfig */,
|
||||
E4D15238BF2B8882453091C1 /* Pods-RunnerTests.debug.xcconfig */,
|
||||
6623077AAAEF559F09EAAEC6 /* Pods-RunnerTests.release.xcconfig */,
|
||||
090C37EAACC6628C1878F26D /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||
);
|
||||
name = Flutter;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146E51CF9000F007C117D = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||
767DA86CBE420F5563DA86EC /* Pods */,
|
||||
EBB65BF4410896E755CE9345 /* Frameworks */,
|
||||
71E56F6CBCC9735A933C8324 /* GoogleService-Info.plist */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146EF1CF9000F007C117D /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146F01CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||
97C147021CF9000F007C117D /* Info.plist */,
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||
);
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EBB65BF4410896E755CE9345 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
44309CC712138A6C295BDD13 /* Pods_Runner.framework */,
|
||||
3A61884E6070EB92C9DA7565 /* Pods_RunnerTests.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||
buildPhases = (
|
||||
EE15A8A241E794CA6B46D3BF /* [CP] Check Pods Manifest.lock */,
|
||||
331C807D294A63A400263BE5 /* Sources */,
|
||||
331C807F294A63A400263BE5 /* Resources */,
|
||||
822140EDA9A65D30FEC797F5 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||
);
|
||||
name = RunnerTests;
|
||||
productName = RunnerTests;
|
||||
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||
productType = "com.apple.product-type.bundle.unit-test";
|
||||
};
|
||||
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||
buildPhases = (
|
||||
FB0BF3469EFA785B86C304D7 /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
978F70D62289D467CBC98B93 /* [CP] Embed Pods Frameworks */,
|
||||
09CC5E7467F1BA4502F52B26 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
331C8080294A63A400263BE5 = {
|
||||
CreatedOnToolsVersion = 14.0;
|
||||
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||
};
|
||||
97C146ED1CF9000F007C117D = {
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
compatibilityVersion = "Xcode 9.3";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 97C146E51CF9000F007C117D;
|
||||
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
331C807F294A63A400263BE5 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||
619C01BB6DEEA093B9F64580 /* GoogleService-Info.plist in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
09CC5E7467F1BA4502F52B26 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||
);
|
||||
name = "Thin Binary";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Run Script";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||
};
|
||||
978F70D62289D467CBC98B93 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
EE15A8A241E794CA6B46D3BF /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
FB0BF3469EFA785B86C304D7 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
331C807D294A63A400263BE5 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C146FB1CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = Main.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
97C147001CF9000F007C117D /* Base */,
|
||||
);
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = L6P4GD56Q6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
331C8088294A63A400263BE5 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = E4D15238BF2B8882453091C1 /* Pods-RunnerTests.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
331C8089294A63A400263BE5 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 6623077AAAEF559F09EAAEC6 /* Pods-RunnerTests.release.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
331C808A294A63A400263BE5 /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 090C37EAACC6628C1878F26D /* Pods-RunnerTests.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp.RunnerTests;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
97C147031CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147041CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = iphoneos;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
97C147061CF9000F007C117D /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = L6P4GD56Q6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
97C147071CF9000F007C117D /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||
DEVELOPMENT_TEAM = L6P4GD56Q6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
331C8088294A63A400263BE5 /* Debug */,
|
||||
331C8089294A63A400263BE5 /* Release */,
|
||||
331C808A294A63A400263BE5 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147031CF9000F007C117D /* Debug */,
|
||||
97C147041CF9000F007C117D /* Release */,
|
||||
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
97C147061CF9000F007C117D /* Debug */,
|
||||
97C147071CF9000F007C117D /* Release */,
|
||||
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,98 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO"
|
||||
parallelizable = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||
BuildableName = "RunnerTests.xctest"
|
||||
BlueprintName = "RunnerTests"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Profile"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:Runner.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PreviewsEnabled</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,13 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 295 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 450 B |
After Width: | Height: | Size: 282 B |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 762 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 68 B |
After Width: | Height: | Size: 68 B |
After Width: | Height: | Size: 68 B |
|
@ -0,0 +1,5 @@
|
|||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchImage" width="168" height="185"/>
|
||||
</resources>
|
||||
</document>
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Flutter View Controller-->
|
||||
<scene sceneID="tne-QT-ifu">
|
||||
<objects>
|
||||
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CLIENT_ID</key>
|
||||
<string>1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5.apps.googleusercontent.com</string>
|
||||
<key>REVERSED_CLIENT_ID</key>
|
||||
<string>com.googleusercontent.apps.1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5</string>
|
||||
<key>ANDROID_CLIENT_ID</key>
|
||||
<string>1072369607324-hp2vujt95ipkft4n348a96ndbiocqrdo.apps.googleusercontent.com</string>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyDI7KTuPhqGiIkX5pznUDYkToUmIhznqf8</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>1072369607324</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.example.forwardChainingManApp</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>sirekomendasi-dc7de</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>sirekomendasi-dc7de.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:1072369607324:ios:1d8cb1dbadb289ff6f4359</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1,69 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||
<string>We need permission to save photos and videos to your library for your convenience.</string>
|
||||
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>We need permission to access your photo library to view and select your photos and videos.</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Forward Chaining Man App</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>forward_chaining_man_app</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<!-- TODO Replace this value: -->
|
||||
<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
|
||||
<string>com.googleusercontent.apps.1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
|
@ -0,0 +1 @@
|
|||
#import "GeneratedPluginRegistrant.h"
|
|
@ -0,0 +1,12 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testExample() {
|
||||
// If you add code to the Runner application, consider adding tests here.
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' as rootBundle;
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// Bagian Model & Fungsi Pendukung
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
/// Representasi data ProgramStudi (bawaan dari JSON)
|
||||
class ProgramStudi {
|
||||
final String name;
|
||||
final String description;
|
||||
final List<String> categories;
|
||||
final Map<String, Minat> minat;
|
||||
|
||||
ProgramStudi({
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.categories,
|
||||
required this.minat,
|
||||
});
|
||||
|
||||
factory ProgramStudi.fromJson(Map<String, dynamic> json) {
|
||||
final minatMap = <String, Minat>{};
|
||||
if (json['minat'] != null) {
|
||||
(json['minat'] as Map<String, dynamic>).forEach((key, value) {
|
||||
minatMap[key] = Minat.fromJson(value);
|
||||
});
|
||||
}
|
||||
|
||||
return ProgramStudi(
|
||||
name: json['name'] ?? '',
|
||||
description: json['description'] ?? '',
|
||||
categories: json['categories'] == null
|
||||
? []
|
||||
: List<String>.from(json['categories']),
|
||||
minat: minatMap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Representasi data Minat (pertanyaan, karir, dsb.)
|
||||
class Minat {
|
||||
final List<String> pertanyaan;
|
||||
final List<String> karir;
|
||||
final List<String> jurusanTerkait;
|
||||
|
||||
Minat({
|
||||
required this.pertanyaan,
|
||||
required this.karir,
|
||||
required this.jurusanTerkait,
|
||||
});
|
||||
|
||||
factory Minat.fromJson(Map<String, dynamic> json) {
|
||||
return Minat(
|
||||
pertanyaan: json['pertanyaan'] == null
|
||||
? []
|
||||
: List<String>.from(json['pertanyaan']),
|
||||
karir: json['karir'] == null ? [] : List<String>.from(json['karir']),
|
||||
jurusanTerkait: json['jurusan_terkait'] == null
|
||||
? []
|
||||
: List<String>.from(json['jurusan_terkait']),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Model flattened satu pertanyaan
|
||||
class QuestionItem {
|
||||
final String id; // unique ID, misal "Q1", "Q2"
|
||||
final String programName; // ex: "IPA (Sains Murni) - Kerja"
|
||||
final String minatKey; // ex: "Kedokteran"
|
||||
final String questionText; // Cleaned question text
|
||||
final String rawQuestionText; // Original question text with code
|
||||
final int bobot; // ex: 5
|
||||
bool? userAnswer; // Jawaban user: true, false, atau null
|
||||
|
||||
// Add this field to store the question code
|
||||
final String questionCode; // ex: "KUL04"
|
||||
|
||||
// Add any other existing fields...
|
||||
|
||||
QuestionItem({
|
||||
required this.id,
|
||||
required this.programName,
|
||||
required this.minatKey,
|
||||
required this.questionText,
|
||||
required this.rawQuestionText,
|
||||
required this.bobot,
|
||||
this.userAnswer,
|
||||
required this.questionCode, // Add to constructor
|
||||
// Add any other existing parameters...
|
||||
});
|
||||
|
||||
// If needed, you can keep a factory method to extract code from raw text
|
||||
factory QuestionItem.fromRawQuestion({
|
||||
required String id,
|
||||
required String programName,
|
||||
required String minatKey,
|
||||
required String questionText,
|
||||
required String rawQuestionText,
|
||||
required int bobot,
|
||||
bool? userAnswer,
|
||||
// Any other existing parameters...
|
||||
}) {
|
||||
// Extract question code
|
||||
final regex = RegExp(r'([A-Z]+\d+):');
|
||||
final match = regex.firstMatch(rawQuestionText);
|
||||
final questionCode = match != null && match.groupCount >= 1
|
||||
? match.group(1)!
|
||||
: id; // Fallback to ID if no code found
|
||||
|
||||
return QuestionItem(
|
||||
id: id,
|
||||
programName: programName,
|
||||
minatKey: minatKey,
|
||||
questionText: questionText,
|
||||
rawQuestionText: rawQuestionText,
|
||||
bobot: bobot,
|
||||
userAnswer: userAnswer,
|
||||
questionCode: questionCode,
|
||||
// Pass any other existing parameters...
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Fungsi untuk mengambil bobot [n] dari string pertanyaan
|
||||
int extractBobot(String pertanyaan) {
|
||||
final regex = RegExp(r"\[(\d+)\]");
|
||||
final match = regex.firstMatch(pertanyaan);
|
||||
if (match != null) {
|
||||
return int.parse(match.group(1)!);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Fungsi untuk menghapus teks [n] di akhir pertanyaan
|
||||
String cleanPertanyaan(String pertanyaan) {
|
||||
return pertanyaan.replaceAll(RegExp(r"\[\d+\]"), "").trim();
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
bool developerMode = false;
|
||||
|
||||
/// Controller untuk DeveloperModePage
|
||||
class DeveloperModeController extends GetxController {
|
||||
final RxBool isDeveloperMode = developerMode.obs;
|
||||
|
||||
void toggleDeveloperMode(bool value) {
|
||||
isDeveloperMode.value = value;
|
||||
developerMode = value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
/// Controller untuk HomePage
|
||||
class HomeController extends GetxController {
|
||||
final Rx<bool?> pilihan =
|
||||
Rx<bool?>(null); // null=belum pilih; true=Kerja; false=Kuliah
|
||||
|
||||
void setPilihan(bool? val) {
|
||||
pilihan.value = val;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,976 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' as rootBundle;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/about/widget/diagram_painter.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_profile.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AboutPage extends StatefulWidget {
|
||||
const AboutPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<AboutPage> createState() => _AboutPageState();
|
||||
}
|
||||
|
||||
class ForwardChainingLogoPainter extends CustomPainter {
|
||||
final double animationValue;
|
||||
|
||||
ForwardChainingLogoPainter({required this.animationValue});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final centerX = size.width / 2;
|
||||
final centerY = size.height / 2;
|
||||
final radius = size.width * 0.32;
|
||||
|
||||
// Create a layered neural network structure with input, hidden, and output nodes
|
||||
|
||||
// Define the layers (3 layers: input, hidden, output)
|
||||
final int inputNodes = 4;
|
||||
final int hiddenNodes = 6;
|
||||
final int outputNodes = 3;
|
||||
|
||||
// Node positions for each layer
|
||||
final List<Offset> inputLayer = [];
|
||||
final List<Offset> hiddenLayer = [];
|
||||
final List<Offset> outputLayer = [];
|
||||
|
||||
// Create a gradient for the background glow
|
||||
final Rect rect =
|
||||
Rect.fromCircle(center: Offset(centerX, centerY), radius: radius * 1.2);
|
||||
final gradient = RadialGradient(
|
||||
colors: [
|
||||
Colors.indigo.shade400.withOpacity(0.2),
|
||||
Colors.transparent,
|
||||
],
|
||||
stops: const [0.5, 1.0],
|
||||
);
|
||||
|
||||
final backgroundPaint = Paint()
|
||||
..shader = gradient.createShader(rect)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(Offset(centerX, centerY), radius * 1.2, backgroundPaint);
|
||||
|
||||
// Paints for nodes
|
||||
final inputNodePaint = Paint()
|
||||
..color = Colors.blue.shade400
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final hiddenNodePaint = Paint()
|
||||
..color = Colors.indigo.shade400
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final outputNodePaint = Paint()
|
||||
..color = Colors.purple.shade400
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
// Paint for node glows
|
||||
final glowPaint = Paint()
|
||||
..color = Colors.indigo.shade200.withOpacity(0.4)
|
||||
..style = PaintingStyle.fill
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8);
|
||||
|
||||
// Paint for node borders
|
||||
final borderPaint = Paint()
|
||||
..color = Colors.white.withOpacity(0.8)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
|
||||
// Paint for connections with animation
|
||||
final connectionPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
|
||||
// Create input layer nodes (left side)
|
||||
final inputSpacing = size.height / (inputNodes + 1);
|
||||
for (int i = 0; i < inputNodes; i++) {
|
||||
final y = (i + 1) * inputSpacing;
|
||||
final pulseFactor = math
|
||||
.sin((animationValue * 2 * math.pi) + (i * 0.5))
|
||||
.clamp(-0.5, 0.5) *
|
||||
0.05;
|
||||
final offsetX = (math.sin(animationValue * math.pi + i) * 4).clamp(-4, 4);
|
||||
|
||||
// Position slightly to the left of center
|
||||
inputLayer.add(Offset(
|
||||
centerX - (radius * 0.7) + offsetX, y + (pulseFactor * size.height)));
|
||||
}
|
||||
|
||||
// Create hidden layer nodes (center)
|
||||
final hiddenSpacing = size.height / (hiddenNodes + 1);
|
||||
for (int i = 0; i < hiddenNodes; i++) {
|
||||
final y = (i + 1) * hiddenSpacing;
|
||||
final pulseFactor = math
|
||||
.sin((animationValue * 2 * math.pi) + (i * 0.7))
|
||||
.clamp(-0.5, 0.5) *
|
||||
0.03;
|
||||
final offsetY =
|
||||
(math.sin(animationValue * math.pi * 2 + i * 0.5) * 5).clamp(-5, 5);
|
||||
|
||||
// Position at center
|
||||
hiddenLayer
|
||||
.add(Offset(centerX + (pulseFactor * size.width), y + offsetY));
|
||||
}
|
||||
|
||||
// Create output layer nodes (right side)
|
||||
final outputSpacing = size.height / (outputNodes + 1);
|
||||
for (int i = 0; i < outputNodes; i++) {
|
||||
final y = (i + 1) * outputSpacing;
|
||||
final pulseFactor = math
|
||||
.sin((animationValue * 2 * math.pi) + (i * 0.9))
|
||||
.clamp(-0.5, 0.5) *
|
||||
0.05;
|
||||
final offsetX =
|
||||
(math.sin(animationValue * math.pi + i * 1.2) * 4).clamp(-4, 4);
|
||||
|
||||
// Position to the right of center
|
||||
outputLayer.add(Offset(
|
||||
centerX + (radius * 0.7) + offsetX, y + (pulseFactor * size.height)));
|
||||
}
|
||||
|
||||
// Draw connections with animated data flow
|
||||
void drawConnections(
|
||||
List<Offset> fromLayer, List<Offset> toLayer, Color baseColor) {
|
||||
for (int i = 0; i < fromLayer.length; i++) {
|
||||
for (int j = 0; j < toLayer.length; j++) {
|
||||
// Create a flow effect along the connection
|
||||
final path = Path();
|
||||
path.moveTo(fromLayer[i].dx, fromLayer[i].dy);
|
||||
path.lineTo(toLayer[j].dx, toLayer[j].dy);
|
||||
|
||||
// Create gradient shader for data flow effect
|
||||
final pathMetrics = path.computeMetrics().first;
|
||||
final length = pathMetrics.length;
|
||||
|
||||
// Animate a dot along the path
|
||||
final flowPosition =
|
||||
(animationValue * 2 + (i * 0.1) + (j * 0.05)) % 1.0;
|
||||
final flowPoint =
|
||||
pathMetrics.getTangentForOffset(length * flowPosition)?.position;
|
||||
|
||||
// Basic line
|
||||
connectionPaint.color = baseColor.withOpacity(0.3 +
|
||||
(0.2 * math.sin(animationValue * math.pi * 2 + i + j))
|
||||
.clamp(0.0, 0.5));
|
||||
canvas.drawPath(path, connectionPaint);
|
||||
|
||||
// Draw data flow point
|
||||
if (flowPoint != null && (i + j) % 2 == 0) {
|
||||
// Only draw on some connections to avoid clutter
|
||||
final flowDotPaint = Paint()
|
||||
..color = Colors.white.withOpacity(0.7)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(flowPoint, 1.5, flowDotPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Draw connections from input to hidden layer
|
||||
drawConnections(inputLayer, hiddenLayer, Colors.blue.shade500);
|
||||
|
||||
// Draw connections from hidden to output layer
|
||||
drawConnections(hiddenLayer, outputLayer, Colors.purple.shade500);
|
||||
|
||||
// Draw the nodes with glow effect
|
||||
void drawNodesWithEffects(
|
||||
List<Offset> nodes, Paint nodePaint, double size) {
|
||||
for (int i = 0; i < nodes.length; i++) {
|
||||
final node = nodes[i];
|
||||
// Size pulsation
|
||||
final pulse =
|
||||
1.0 + 0.15 * math.sin((animationValue * 2 * math.pi) + (i));
|
||||
final nodeSize = size * pulse.clamp(0.9, 1.15);
|
||||
|
||||
// Draw glow
|
||||
canvas.drawCircle(node, nodeSize * 1.5, glowPaint);
|
||||
|
||||
// Draw node
|
||||
canvas.drawCircle(node, nodeSize, nodePaint);
|
||||
|
||||
// Draw border
|
||||
canvas.drawCircle(node, nodeSize, borderPaint);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw all nodes by layer
|
||||
drawNodesWithEffects(inputLayer, inputNodePaint, 5);
|
||||
drawNodesWithEffects(hiddenLayer, hiddenNodePaint, 6);
|
||||
drawNodesWithEffects(outputLayer, outputNodePaint, 5);
|
||||
|
||||
// Draw central circle highlight
|
||||
final centerGlowPaint = Paint()
|
||||
..color = Colors.indigo.withOpacity(
|
||||
(0.1 + 0.05 * math.sin(animationValue * math.pi * 2))
|
||||
.clamp(0.05, 0.15))
|
||||
..style = PaintingStyle.fill
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 15);
|
||||
|
||||
canvas.drawCircle(Offset(centerX, centerY), radius * 0.4, centerGlowPaint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(ForwardChainingLogoPainter oldDelegate) {
|
||||
return oldDelegate.animationValue != animationValue;
|
||||
}
|
||||
}
|
||||
|
||||
class _AboutPageState extends State<AboutPage> with TickerProviderStateMixin {
|
||||
late AnimationController _logoAnimationController;
|
||||
late AnimationController _cardAnimationController;
|
||||
late Animation<double> _logoRotationAnimation;
|
||||
late Animation<double> _logoScaleAnimation;
|
||||
late Animation<double> _fadeInAnimation;
|
||||
late List<Animation<Offset>> _cardSlideAnimations;
|
||||
|
||||
// For step animation
|
||||
int _currentStep = 0;
|
||||
final int _totalSteps = 4;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Logo animation controller
|
||||
_logoAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 3),
|
||||
)..repeat(reverse: true);
|
||||
|
||||
// Card animation controller
|
||||
_cardAnimationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 1200),
|
||||
);
|
||||
|
||||
// Logo animations
|
||||
// Fix: Ensure the end value is <= 1.0 to prevent the assertion error
|
||||
_logoRotationAnimation = Tween<double>(begin: 0, end: 0.05).animate(
|
||||
CurvedAnimation(
|
||||
parent: _logoAnimationController,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
|
||||
// Fix: Ensure the end value doesn't cause any curves to go beyond 1.0
|
||||
_logoScaleAnimation = Tween<double>(begin: 1.0, end: 1.08).animate(
|
||||
CurvedAnimation(
|
||||
parent: _logoAnimationController,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
|
||||
_fadeInAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||
CurvedAnimation(
|
||||
parent: _cardAnimationController,
|
||||
// Fix: Ensure the end of the interval is <= 1.0
|
||||
curve: const Interval(0, 0.6, curve: Curves.easeOut),
|
||||
),
|
||||
);
|
||||
|
||||
// Card slide animations (staggered)
|
||||
_cardSlideAnimations = [
|
||||
for (int i = 0; i < 5; i++)
|
||||
Tween<Offset>(
|
||||
begin: const Offset(0, 0.5),
|
||||
end: Offset.zero,
|
||||
).animate(
|
||||
CurvedAnimation(
|
||||
parent: _cardAnimationController,
|
||||
curve: Interval(
|
||||
0.2 + (i * 0.12),
|
||||
(0.7 + (i * 0.08)).clamp(0.0, 1.0), // ✅ Perbaikan utama!
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
// Start animations
|
||||
_cardAnimationController.forward();
|
||||
|
||||
// Start step animation
|
||||
Future.delayed(const Duration(seconds: 2), () {
|
||||
_startStepAnimation();
|
||||
});
|
||||
}
|
||||
|
||||
void _startStepAnimation() {
|
||||
Future.delayed(const Duration(seconds: 3), () {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_currentStep = (_currentStep + 1) % _totalSteps;
|
||||
});
|
||||
_startStepAnimation();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_logoAnimationController.dispose();
|
||||
_cardAnimationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.indigo.shade900,
|
||||
Colors.blue.shade800,
|
||||
Colors.blue.shade700,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: [
|
||||
// App Bar
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const Text(
|
||||
'Tentang Aplikasi',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(width: 40), // Balance the layout
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
// Animated Logo
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Title and Tagline
|
||||
FadeTransition(
|
||||
opacity: _fadeInAnimation,
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'EduGuide',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
),
|
||||
child: const Text(
|
||||
'Sistem Rekomendasi Karir & Jurusan',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// About App Card
|
||||
SlideTransition(
|
||||
position: _cardSlideAnimations[0],
|
||||
child: _buildInfoCard(
|
||||
title: 'Tentang Aplikasi',
|
||||
icon: Icons.info_outline,
|
||||
color: Colors.blue.shade300,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Aplikasi EduGuide adalah sistem pakar berbasis aturan (rule-based expert system) yang menggunakan metode inferensi forward chaining untuk memberikan rekomendasi jurusan dan karir yang sesuai dengan minat pengguna.',
|
||||
style: TextStyle(fontSize: 14, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Aplikasi ini dikembangkan sebagai bagian dari tugas akhir/skripsi untuk menunjukkan implementasi praktis dari metode forward chaining dalam sistem pendukung keputusan.',
|
||||
style: TextStyle(fontSize: 14, height: 1.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// How It Works Card
|
||||
SlideTransition(
|
||||
position: _cardSlideAnimations[1],
|
||||
child: _buildInfoCard(
|
||||
title: 'Cara Kerja Forward Chaining',
|
||||
icon: Icons.lightbulb_outline,
|
||||
color: Colors.orange.shade300,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Forward Chaining adalah metode penalaran dari fakta-fakta yang diketahui menuju kesimpulan. Dalam aplikasi ini:',
|
||||
style: TextStyle(fontSize: 14, height: 1.5),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Animated steps
|
||||
_buildAnimatedStepExplanation(),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Colors.amber.shade200,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.tips_and_updates,
|
||||
color: Colors.amber.shade700,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'Dengan metode ini, sistem dapat memberikan rekomendasi yang paling sesuai berdasarkan minat kamu!',
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Forward Chaining Visual Explanation
|
||||
SlideTransition(
|
||||
position: _cardSlideAnimations[2],
|
||||
child: _buildInfoCard(
|
||||
title: 'Visualisasi Proses',
|
||||
icon: Icons.bar_chart,
|
||||
color: Colors.green.shade300,
|
||||
child: Column(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/forward_chaining_diagram.png',
|
||||
fit: BoxFit.contain,
|
||||
height: 180,
|
||||
errorBuilder: (context, error, stackTrace) {
|
||||
// Fallback if image not available
|
||||
return _buildForwardChainingDiagram();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const Text(
|
||||
'Forward Chaining bekerja dengan mengevaluasi jawaban kamu dan mencocokkannya dengan aturan (rules) untuk menemukan rekomendasi terbaik. Ini seperti menyelesaikan teka-teki dengan petunjuk yang kamu berikan.',
|
||||
style: TextStyle(fontSize: 14, height: 1.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Tech Stack Card
|
||||
SlideTransition(
|
||||
position: _cardSlideAnimations[3],
|
||||
child: _buildInfoCard(
|
||||
title: 'Teknologi',
|
||||
icon: Icons.code,
|
||||
color: Colors.purple.shade300,
|
||||
child: Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
_buildTechChip(
|
||||
label: 'Flutter', icon: Icons.flutter_dash),
|
||||
_buildTechChip(
|
||||
label: 'Dart', icon: Icons.extension),
|
||||
_buildTechChip(
|
||||
label: 'Forward Chaining',
|
||||
icon: Icons.account_tree),
|
||||
_buildTechChip(
|
||||
label: 'GetX', icon: Icons.auto_awesome),
|
||||
_buildTechChip(
|
||||
label: 'Rule-Based System', icon: Icons.rule),
|
||||
_buildTechChip(
|
||||
label: 'Expert System',
|
||||
icon: Icons.psychology),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Developer Card
|
||||
SlideTransition(
|
||||
position: _cardSlideAnimations[4],
|
||||
child: _buildInfoCard(
|
||||
title: 'Pengembang',
|
||||
icon: Icons.person,
|
||||
color: Colors.amber.shade300,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 80,
|
||||
height: 80,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 8,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
image: const DecorationImage(
|
||||
image: AssetImage('assets/profile_dev.png'),
|
||||
fit: BoxFit.cover,
|
||||
// Use a placeholder if no image is available
|
||||
onError: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Yanuar Tri Laksono',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Mahasiswa Informatika',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
SocialButton(
|
||||
icon: FontAwesomeIcons
|
||||
.envelope, // Email icon
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse(
|
||||
'mailto:yanuartrilaksono23@gmail.com'));
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SocialButton(
|
||||
icon: FontAwesomeIcons
|
||||
.linkedin, // Portfolio link icon
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse(
|
||||
'https://www.linkedin.com/in/yanuar-tri-laksono/'));
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
SocialButton(
|
||||
icon: FontAwesomeIcons
|
||||
.github, // GitHub icon
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse(
|
||||
'https://github.com/Greek-Cp'));
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Footer
|
||||
FadeTransition(
|
||||
opacity: _fadeInAnimation,
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
'© ${DateTime.now().year} EduGuide App',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Versi 1.0.0',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.6),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Build the animated step explanation
|
||||
Widget _buildAnimatedStepExplanation() {
|
||||
final List<Map<String, dynamic>> steps = [
|
||||
{
|
||||
'icon': Icons.question_answer,
|
||||
'title': 'Langkah 1: Mengumpulkan Fakta',
|
||||
'content':
|
||||
'Sistem mengumpulkan jawaban "Ya" atau "Tidak" dari semua pertanyaanmu dan menyimpannya sebagai fakta.',
|
||||
'color': Colors.blue.shade700,
|
||||
},
|
||||
{
|
||||
'icon': Icons.rule,
|
||||
'title': 'Langkah 2: Mencocokkan Aturan',
|
||||
'content':
|
||||
'Sistem mencocokkan jawabanmu dengan aturan-aturan minat dan karir. Setiap jawaban "Ya" akan menambah skor pada minat tertentu.',
|
||||
'color': Colors.green.shade700,
|
||||
},
|
||||
{
|
||||
'icon': Icons.calculate,
|
||||
'title': 'Langkah 3: Menghitung Skor',
|
||||
'content':
|
||||
'Skor untuk setiap minat dan karir dihitung berdasarkan pertanyaan yang kamu jawab "Ya".',
|
||||
'color': Colors.orange.shade700,
|
||||
},
|
||||
{
|
||||
'icon': Icons.star,
|
||||
'title': 'Langkah 4: Memberikan Rekomendasi',
|
||||
'content':
|
||||
'Sistem mengurutkan hasil dan menampilkan 3 minat dengan skor tertinggi sebagai rekomendasi terbaikmu.',
|
||||
'color': Colors.purple.shade700,
|
||||
},
|
||||
];
|
||||
|
||||
return Column(
|
||||
children: steps.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final step = entry.value;
|
||||
final isActive = index == _currentStep;
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isActive ? step['color'].withOpacity(0.1) : Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isActive ? step['color'] : Colors.grey.shade200,
|
||||
width: isActive ? 2 : 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? step['color'] : Colors.grey.shade200,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
step['icon'],
|
||||
color: isActive ? Colors.white : Colors.grey.shade600,
|
||||
size: 18,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
step['title'],
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 14,
|
||||
color: isActive ? step['color'] : Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
step['content'],
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
height: 1.4,
|
||||
color: isActive ? Colors.black87 : Colors.grey.shade700,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback Forward Chaining diagram if image is not available
|
||||
Widget _buildForwardChainingDiagram() {
|
||||
return Container(
|
||||
height: 180,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: CustomPaint(
|
||||
painter: ForwardChainingDiagramPainter(),
|
||||
size: const Size(double.infinity, 180),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Animated logo content with nodes and connections
|
||||
Widget _buildAnimatedLogoContent() {
|
||||
return CustomPaint(
|
||||
painter: ForwardChainingLogoPainter(
|
||||
animationValue: _logoAnimationController.value,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.psychology,
|
||||
size: 70,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Card widget with consistent styling
|
||||
Widget _buildInfoCard({
|
||||
required String title,
|
||||
required IconData icon,
|
||||
required Color color,
|
||||
required Widget child,
|
||||
}) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Card Header
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.2),
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(24),
|
||||
topRight: Radius.circular(24),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Card Content
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Tech stack chip
|
||||
Widget _buildTechChip({required String label, required IconData icon}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade300,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 16,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.grey.shade800,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Social media/contact button
|
||||
Widget _buildSocialButton({
|
||||
required IconData icon,
|
||||
required VoidCallback onTap,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 18,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SocialButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const SocialButton({
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade200,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: FaIcon(
|
||||
icon,
|
||||
size: 18,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' as rootBundle;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_profile.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ForwardChainingDiagramPainter extends CustomPainter {
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final width = size.width;
|
||||
final height = size.height;
|
||||
|
||||
// Define colors
|
||||
final workingMemoryColor = Colors.blue.shade100;
|
||||
final rulesColor = Colors.orange.shade100;
|
||||
final resultsColor = Colors.green.shade100;
|
||||
final arrowColor = Colors.grey.shade700;
|
||||
|
||||
// Define paint objects
|
||||
final boxPaint = Paint()..style = PaintingStyle.fill;
|
||||
final borderPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = Colors.grey.shade600
|
||||
..strokeWidth = 1.5;
|
||||
|
||||
final textStyle = TextStyle(
|
||||
color: Colors.black87,
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w500,
|
||||
);
|
||||
|
||||
final titleStyle = TextStyle(
|
||||
color: Colors.black,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
|
||||
// Helper function to draw boxes with text
|
||||
void drawBox(String title, String content, Rect rect, Color color) {
|
||||
// Draw box
|
||||
boxPaint.color = color;
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, Radius.circular(8)),
|
||||
boxPaint,
|
||||
);
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect, Radius.circular(8)),
|
||||
borderPaint,
|
||||
);
|
||||
|
||||
// Draw title
|
||||
final titleSpan = TextSpan(text: title, style: titleStyle);
|
||||
final titlePainter = TextPainter(
|
||||
text: titleSpan,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
titlePainter.layout(maxWidth: rect.width - 10);
|
||||
titlePainter.paint(
|
||||
canvas,
|
||||
Offset(rect.left + 5, rect.top + 5),
|
||||
);
|
||||
|
||||
// Draw content
|
||||
final contentSpan = TextSpan(text: content, style: textStyle);
|
||||
final contentPainter = TextPainter(
|
||||
text: contentSpan,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
contentPainter.layout(maxWidth: rect.width - 10);
|
||||
contentPainter.paint(
|
||||
canvas,
|
||||
Offset(rect.left + 5, rect.top + 25),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper to draw arrows
|
||||
void drawArrow(Offset start, Offset end) {
|
||||
final paint = Paint()
|
||||
..color = arrowColor
|
||||
..strokeWidth = 1.5
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
// Draw line
|
||||
canvas.drawLine(start, end, paint);
|
||||
|
||||
// Draw arrowhead
|
||||
final delta = end - start;
|
||||
final angle = delta.direction;
|
||||
final arrowSize = 8.0;
|
||||
|
||||
final arrowPath = Path()
|
||||
..moveTo(end.dx, end.dy)
|
||||
..lineTo(
|
||||
end.dx - arrowSize * math.cos(angle - math.pi / 6),
|
||||
end.dy - arrowSize * math.sin(angle - math.pi / 6),
|
||||
)
|
||||
..lineTo(
|
||||
end.dx - arrowSize * math.cos(angle + math.pi / 6),
|
||||
end.dy - arrowSize * math.sin(angle + math.pi / 6),
|
||||
)
|
||||
..close();
|
||||
|
||||
canvas.drawPath(arrowPath, Paint()..color = arrowColor);
|
||||
}
|
||||
|
||||
// Calculate box positions
|
||||
final workingMemoryRect = Rect.fromLTWH(20, 20, width * 0.25, height - 40);
|
||||
final rulesRect =
|
||||
Rect.fromLTWH(width * 0.33, 20, width * 0.25, height - 40);
|
||||
final resultsRect =
|
||||
Rect.fromLTWH(width * 0.66, 20, width * 0.25, height - 40);
|
||||
|
||||
// Draw the boxes
|
||||
drawBox(
|
||||
'Working Memory',
|
||||
'Facts:\n• Q1=Yes\n• Q2=No\n• Q3=Yes\n...',
|
||||
workingMemoryRect,
|
||||
workingMemoryColor,
|
||||
);
|
||||
|
||||
drawBox(
|
||||
'Rules',
|
||||
'IF Q1=Yes THEN Skor+3\nIF Q2=Yes THEN Skor+2\nIF Q3=Yes AND Q4=Yes\n THEN Skor+4\n...',
|
||||
rulesRect,
|
||||
rulesColor,
|
||||
);
|
||||
|
||||
drawBox(
|
||||
'Results (Top 3)',
|
||||
'1. IPA | Kedokteran (24)\n2. IPS | Ekonomi (19)\n3. IPA | Teknik (16)',
|
||||
resultsRect,
|
||||
resultsColor,
|
||||
);
|
||||
|
||||
// Draw arrows
|
||||
drawArrow(
|
||||
Offset(workingMemoryRect.right, workingMemoryRect.center.dy - 20),
|
||||
Offset(rulesRect.left, rulesRect.center.dy - 20),
|
||||
);
|
||||
|
||||
drawArrow(
|
||||
Offset(rulesRect.right, rulesRect.center.dy),
|
||||
Offset(resultsRect.left, resultsRect.center.dy),
|
||||
);
|
||||
|
||||
// Draw loop arrow for rule evaluation
|
||||
final loopStart = Offset(rulesRect.right - 20, rulesRect.bottom - 25);
|
||||
final loopControl1 = Offset(rulesRect.right + 20, rulesRect.bottom + 15);
|
||||
final loopControl2 = Offset(rulesRect.left - 20, rulesRect.bottom + 15);
|
||||
final loopEnd = Offset(rulesRect.left, rulesRect.bottom - 25);
|
||||
|
||||
final loopPath = Path()
|
||||
..moveTo(loopStart.dx, loopStart.dy)
|
||||
..cubicTo(
|
||||
loopControl1.dx,
|
||||
loopControl1.dy,
|
||||
loopControl2.dx,
|
||||
loopControl2.dy,
|
||||
loopEnd.dx,
|
||||
loopEnd.dy,
|
||||
);
|
||||
|
||||
canvas.drawPath(
|
||||
loopPath,
|
||||
Paint()
|
||||
..color = arrowColor
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2,
|
||||
);
|
||||
|
||||
// Draw arrowhead for loop
|
||||
final loopDelta = Offset(0, -5);
|
||||
final loopAngle = loopDelta.direction;
|
||||
final arrowSize = 7.0;
|
||||
|
||||
final loopArrowPath = Path()
|
||||
..moveTo(loopEnd.dx, loopEnd.dy)
|
||||
..lineTo(
|
||||
loopEnd.dx - arrowSize * math.cos(loopAngle - math.pi / 6),
|
||||
loopEnd.dy - arrowSize * math.sin(loopAngle - math.pi / 6),
|
||||
)
|
||||
..lineTo(
|
||||
loopEnd.dx - arrowSize * math.cos(loopAngle + math.pi / 6),
|
||||
loopEnd.dy - arrowSize * math.sin(loopAngle + math.pi / 6),
|
||||
)
|
||||
..close();
|
||||
|
||||
canvas.drawPath(loopArrowPath, Paint()..color = arrowColor);
|
||||
|
||||
// Add "Rule Evaluation Loop" text
|
||||
final loopTextSpan = TextSpan(
|
||||
text: "Rule Evaluation Loop",
|
||||
style: TextStyle(
|
||||
color: Colors.grey.shade800,
|
||||
fontSize: 9,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
);
|
||||
|
||||
final loopTextPainter = TextPainter(
|
||||
text: loopTextSpan,
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
loopTextPainter.layout();
|
||||
loopTextPainter.paint(
|
||||
canvas, Offset(rulesRect.center.dx - 35, rulesRect.bottom + 5));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
|
@ -0,0 +1,817 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class ProfileController extends GetxController {
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
|
||||
var isLoading = false.obs;
|
||||
var isEditing = false.obs;
|
||||
var userProfile = Rx<Map<String, dynamic>>({});
|
||||
var schoolId = ''.obs;
|
||||
|
||||
final nameController = TextEditingController();
|
||||
final selectedClass = Rx<String?>(null);
|
||||
|
||||
// Class options for the dropdown
|
||||
final List<String> classOptions = [
|
||||
'X IPA A (1)',
|
||||
'X IPA B (2)',
|
||||
'X IPA C (3)',
|
||||
'X IPA D (4)',
|
||||
'XI IPA A (1)',
|
||||
'XI IPA B (2)',
|
||||
'XI IPA C (3)',
|
||||
'XI IPA D (4)',
|
||||
'XII IPA A (1)',
|
||||
'XII IPA B (2)',
|
||||
'XII IPA C (3)',
|
||||
'XII IPA D (4)',
|
||||
];
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
loadUserProfile();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
nameController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future<void> loadUserProfile() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
User? currentUser = _auth.currentUser;
|
||||
if (currentUser == null) {
|
||||
Get.offAll(() => StudentLoginPage());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get schoolId from SharedPreferences
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final storedSchoolId = prefs.getString('school_id');
|
||||
|
||||
if (storedSchoolId != null && storedSchoolId.isNotEmpty) {
|
||||
schoolId.value = storedSchoolId;
|
||||
|
||||
// Get student data from the subcollection
|
||||
final docSnapshot = await _firestore
|
||||
.collection('schools')
|
||||
.doc(schoolId.value)
|
||||
.collection('students')
|
||||
.doc(currentUser.uid)
|
||||
.get();
|
||||
|
||||
if (docSnapshot.exists) {
|
||||
userProfile.value = docSnapshot.data() as Map<String, dynamic>;
|
||||
|
||||
// Initialize controllers with current values
|
||||
nameController.text = userProfile.value['name'] ?? '';
|
||||
selectedClass.value = userProfile.value['class'];
|
||||
} else {
|
||||
// Student not found in this school
|
||||
await _findStudentInAllSchools(currentUser);
|
||||
}
|
||||
} else {
|
||||
// School ID not found in shared preferences, search in all schools
|
||||
await _findStudentInAllSchools(currentUser);
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal memuat profil: ${e.toString()}',
|
||||
backgroundColor: Colors.red.shade100,
|
||||
colorText: Colors.red.shade800,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to find a student in all schools
|
||||
Future<void> _findStudentInAllSchools(User currentUser) async {
|
||||
try {
|
||||
final schoolsSnapshot = await _firestore.collection('schools').get();
|
||||
bool found = false;
|
||||
|
||||
for (var schoolDoc in schoolsSnapshot.docs) {
|
||||
final studentDoc = await schoolDoc.reference
|
||||
.collection('students')
|
||||
.doc(currentUser.uid)
|
||||
.get();
|
||||
|
||||
if (studentDoc.exists) {
|
||||
// Found the student
|
||||
schoolId.value = schoolDoc.id;
|
||||
userProfile.value = studentDoc.data() as Map<String, dynamic>;
|
||||
|
||||
// Save school ID to SharedPreferences
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('school_id', schoolId.value);
|
||||
|
||||
// Initialize controllers with current values
|
||||
nameController.text = userProfile.value['name'] ?? '';
|
||||
selectedClass.value = userProfile.value['class'];
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If student was not found in any school
|
||||
if (!found) {
|
||||
nameController.text = currentUser.displayName ?? '';
|
||||
selectedClass.value = null;
|
||||
|
||||
Get.snackbar(
|
||||
'Perhatian',
|
||||
'Profil tidak ditemukan. Silakan lengkapi data profil Anda.',
|
||||
backgroundColor: Colors.orange.shade100,
|
||||
colorText: Colors.orange.shade800,
|
||||
);
|
||||
|
||||
// Enter edit mode automatically
|
||||
isEditing.value = true;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error finding student in schools: $e');
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
void toggleEditMode() {
|
||||
isEditing.value = !isEditing.value;
|
||||
|
||||
// Reset controllers to current values when canceling edit
|
||||
if (!isEditing.value) {
|
||||
nameController.text = userProfile.value['name'] ?? '';
|
||||
selectedClass.value = userProfile.value['class'];
|
||||
} else {
|
||||
selectedClass.value = "";
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveProfile() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
if (nameController.text.trim().isEmpty ||
|
||||
selectedClass.value == null ||
|
||||
selectedClass.value!.isEmpty) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Nama dan kelas tidak boleh kosong',
|
||||
backgroundColor: Colors.red.shade100,
|
||||
colorText: Colors.red.shade800,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
User? currentUser = _auth.currentUser;
|
||||
if (currentUser == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If schoolId is empty, we need to select a school
|
||||
if (schoolId.value.isEmpty) {
|
||||
// For simplicity, using the first school available
|
||||
// In a real app, you might want to show a school selection UI
|
||||
final schoolsSnapshot = await _firestore.collection('schools').get();
|
||||
if (schoolsSnapshot.docs.isNotEmpty) {
|
||||
schoolId.value = schoolsSnapshot.docs.first.id;
|
||||
|
||||
// Save school ID to SharedPreferences
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('school_id', schoolId.value);
|
||||
} else {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Tidak ada sekolah yang tersedia',
|
||||
backgroundColor: Colors.red.shade100,
|
||||
colorText: Colors.red.shade800,
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update Firestore in the appropriate subcollection
|
||||
await _firestore
|
||||
.collection('schools')
|
||||
.doc(schoolId.value)
|
||||
.collection('students')
|
||||
.doc(currentUser.uid)
|
||||
.set({
|
||||
'name': nameController.text.trim(),
|
||||
'class': selectedClass.value,
|
||||
'email': currentUser.email,
|
||||
'photoURL': currentUser.photoURL,
|
||||
'updatedAt': FieldValue.serverTimestamp(),
|
||||
}, SetOptions(merge: true));
|
||||
|
||||
// Update display name if needed
|
||||
if (currentUser.displayName != nameController.text.trim()) {
|
||||
await currentUser.updateDisplayName(nameController.text.trim());
|
||||
}
|
||||
|
||||
// Reload profile
|
||||
await loadUserProfile();
|
||||
|
||||
// Exit edit mode
|
||||
isEditing.value = false;
|
||||
|
||||
Get.snackbar(
|
||||
'Sukses',
|
||||
'Profil berhasil diperbarui',
|
||||
backgroundColor: Colors.green.shade100,
|
||||
colorText: Colors.green.shade800,
|
||||
);
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal menyimpan profil: ${e.toString()}',
|
||||
backgroundColor: Colors.red.shade100,
|
||||
colorText: Colors.red.shade800,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
// Clear shared preferences
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.clear();
|
||||
|
||||
// Sign out from Firebase
|
||||
await _auth.signOut();
|
||||
|
||||
// Navigate to login page
|
||||
Get.offAll(() => IntroPage());
|
||||
} catch (e) {
|
||||
Get.snackbar(
|
||||
'Error',
|
||||
'Gagal keluar: ${e.toString()}',
|
||||
backgroundColor: Colors.red.shade100,
|
||||
colorText: Colors.red.shade800,
|
||||
);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ProfilePage extends StatelessWidget {
|
||||
const ProfilePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(ProfileController());
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text(
|
||||
'Profil Saya',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
backgroundColor: Colors.indigo.shade800,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
actions: [
|
||||
Obx(
|
||||
() => controller.isEditing.value
|
||||
? IconButton(
|
||||
onPressed: controller.toggleEditMode,
|
||||
icon: const Icon(Icons.close),
|
||||
tooltip: 'Batal',
|
||||
)
|
||||
: IconButton(
|
||||
onPressed: controller.toggleEditMode,
|
||||
icon: const Icon(Icons.edit),
|
||||
tooltip: 'Edit Profil',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.indigo.shade800,
|
||||
Colors.blue.shade800,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Obx(
|
||||
() => controller.isLoading.value
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
children: [
|
||||
// Profile Header
|
||||
_buildProfileHeader(controller),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Profile Content
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Informasi Profil',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.indigo.shade800,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Profile Fields
|
||||
Obx(
|
||||
() => controller.isEditing.value
|
||||
? _buildEditFields(controller)
|
||||
: _buildDisplayFields(controller),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Save Button (only in edit mode)
|
||||
Obx(
|
||||
() => controller.isEditing.value
|
||||
? ElevatedButton(
|
||||
onPressed: controller.saveProfile,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor:
|
||||
Colors.indigo.shade800,
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
vertical: 16),
|
||||
minimumSize: const Size(
|
||||
double.infinity, 56),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius:
|
||||
BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: const [
|
||||
Icon(Icons.save, size: 20),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'Simpan Perubahan',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Logout Button
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.dialog(
|
||||
AlertDialog(
|
||||
title: const Text('Konfirmasi'),
|
||||
content: const Text(
|
||||
'Apakah Anda yakin ingin keluar?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('Batal'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
controller.logout();
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
child: const Text('Keluar'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.red.shade50,
|
||||
foregroundColor: Colors.red.shade700,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 16),
|
||||
minimumSize:
|
||||
const Size(double.infinity, 56),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 0,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: const [
|
||||
Icon(Icons.logout, size: 20),
|
||||
SizedBox(width: 10),
|
||||
Text(
|
||||
'Keluar Akun',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// App version
|
||||
Center(
|
||||
child: Text(
|
||||
'App Version 1.0.0',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey.shade500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildProfileHeader(ProfileController controller) {
|
||||
final User? currentUser = FirebaseAuth.instance.currentUser;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Profile Picture
|
||||
CircleAvatar(
|
||||
radius: 50,
|
||||
backgroundColor: Colors.white,
|
||||
backgroundImage: currentUser?.photoURL != null
|
||||
? NetworkImage(currentUser!.photoURL!)
|
||||
: null,
|
||||
child: currentUser?.photoURL == null
|
||||
? Text(
|
||||
controller.userProfile.value['name'] != null
|
||||
? controller.userProfile.value['name']
|
||||
.substring(0, 1)
|
||||
.toUpperCase()
|
||||
: '?',
|
||||
style: const TextStyle(
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.indigo,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Name
|
||||
Text(
|
||||
controller.userProfile.value['name'] ?? 'Siswa',
|
||||
style: const TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 4),
|
||||
|
||||
// Email
|
||||
Text(
|
||||
currentUser?.email ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Class Badge
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.school,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
controller.userProfile.value['class'] ?? 'Kelas tidak diatur',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDisplayFields(ProfileController controller) {
|
||||
final User? currentUser = FirebaseAuth.instance.currentUser;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// Name field
|
||||
_buildInfoItem(
|
||||
icon: Icons.person,
|
||||
title: 'Nama Lengkap',
|
||||
value: controller.userProfile.value['name'] ?? '-',
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Class field
|
||||
_buildInfoItem(
|
||||
icon: Icons.school,
|
||||
title: 'Kelas',
|
||||
value: controller.userProfile.value['class'] ?? '-',
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Email field
|
||||
_buildInfoItem(
|
||||
icon: Icons.email,
|
||||
title: 'Email',
|
||||
value: currentUser?.email ?? '-',
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Registration Type
|
||||
_buildInfoItem(
|
||||
icon: Icons.login,
|
||||
title: 'Jenis Registrasi',
|
||||
value: controller.userProfile.value['registrationType'] == 'google'
|
||||
? 'Google'
|
||||
: 'Email & Password',
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Join Date
|
||||
_buildInfoItem(
|
||||
icon: Icons.calendar_today,
|
||||
title: 'Tanggal Bergabung',
|
||||
value: controller.userProfile.value['createdAt'] != null
|
||||
? _formatTimestamp(controller.userProfile.value['createdAt'])
|
||||
: '-',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEditFields(ProfileController controller) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Name field
|
||||
const Text(
|
||||
'Nama Lengkap',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: controller.nameController,
|
||||
decoration: InputDecoration(
|
||||
prefixIcon: Icon(Icons.person, color: Colors.indigo.shade300),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
borderSide: BorderSide(color: Colors.indigo.shade500),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: Colors.grey.shade50,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// Class dropdown
|
||||
const Text(
|
||||
'Kelas',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.grey.shade300),
|
||||
),
|
||||
child: Obx(() {
|
||||
// Validate that value exists in items list
|
||||
final currentValue = controller.selectedClass.value;
|
||||
final isValueValid = currentValue != null &&
|
||||
controller.classOptions.contains(currentValue);
|
||||
|
||||
return DropdownButtonHideUnderline(
|
||||
child: DropdownButton<String>(
|
||||
// Only set value if it's valid
|
||||
value: isValueValid ? currentValue : null,
|
||||
hint: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.school_outlined,
|
||||
color: Colors.indigo.shade300),
|
||||
const SizedBox(width: 12),
|
||||
const Text('Pilih Kelas'),
|
||||
],
|
||||
),
|
||||
),
|
||||
isExpanded: true,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: Icon(Icons.arrow_drop_down,
|
||||
color: Colors.indigo.shade400),
|
||||
),
|
||||
items: controller.classOptions.map((String kelas) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: kelas,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.school_outlined,
|
||||
color: Colors.indigo.shade300),
|
||||
const SizedBox(width: 12),
|
||||
Text(kelas),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (String? newValue) {
|
||||
controller.selectedClass.value = newValue;
|
||||
},
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoItem({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
required String value,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.indigo.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
color: Colors.indigo.shade400,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatTimestamp(dynamic timestamp) {
|
||||
if (timestamp == null) return '-';
|
||||
|
||||
try {
|
||||
if (timestamp is Timestamp) {
|
||||
final DateTime dateTime = timestamp.toDate();
|
||||
return '${dateTime.day}/${dateTime.month}/${dateTime.year}';
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error formatting timestamp: $e');
|
||||
}
|
||||
|
||||
return '-';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,465 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
|
||||
class TeacherRegisterController extends GetxController {
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
|
||||
var isLoading = false.obs;
|
||||
var errorMessage = ''.obs;
|
||||
|
||||
// Text controllers for registration form
|
||||
final nameController = TextEditingController();
|
||||
final emailController = TextEditingController();
|
||||
final passwordController = TextEditingController();
|
||||
final confirmPasswordController = TextEditingController();
|
||||
final schoolController = TextEditingController();
|
||||
final subjectController = TextEditingController();
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
nameController.dispose();
|
||||
emailController.dispose();
|
||||
passwordController.dispose();
|
||||
confirmPasswordController.dispose();
|
||||
schoolController.dispose();
|
||||
subjectController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// Register new teacher account
|
||||
Future<void> registerTeacher() async {
|
||||
try {
|
||||
// Reset error message
|
||||
errorMessage.value = '';
|
||||
|
||||
// Basic validation
|
||||
if (nameController.text.trim().isEmpty ||
|
||||
emailController.text.trim().isEmpty ||
|
||||
passwordController.text.isEmpty ||
|
||||
confirmPasswordController.text.isEmpty ||
|
||||
schoolController.text.trim().isEmpty) {
|
||||
errorMessage.value = 'Semua kolom wajib diisi';
|
||||
return;
|
||||
}
|
||||
|
||||
// Password validation
|
||||
if (passwordController.text.length < 6) {
|
||||
errorMessage.value = 'Password minimal 6 karakter';
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm password validation
|
||||
if (passwordController.text != confirmPasswordController.text) {
|
||||
errorMessage.value = 'Password tidak sama';
|
||||
return;
|
||||
}
|
||||
|
||||
// Start loading
|
||||
isLoading.value = true;
|
||||
|
||||
// Create user in Firebase Auth
|
||||
final userCredential = await _auth.createUserWithEmailAndPassword(
|
||||
email: emailController.text.trim(),
|
||||
password: passwordController.text,
|
||||
);
|
||||
|
||||
if (userCredential.user != null) {
|
||||
// Add teacher data to Firestore
|
||||
await _firestore
|
||||
.collection('teachers')
|
||||
.doc(userCredential.user!.uid)
|
||||
.set({
|
||||
'name': nameController.text.trim(),
|
||||
'email': emailController.text.trim(),
|
||||
'school': schoolController.text.trim(),
|
||||
'subject': subjectController.text.trim(),
|
||||
'createdAt': FieldValue.serverTimestamp(),
|
||||
'lastLogin': FieldValue.serverTimestamp(),
|
||||
'role': 'teacher',
|
||||
});
|
||||
|
||||
// Show success message
|
||||
Get.snackbar(
|
||||
'Berhasil',
|
||||
'Akun guru berhasil dibuat',
|
||||
backgroundColor: Colors.green.shade100,
|
||||
colorText: Colors.green.shade800,
|
||||
snackPosition: SnackPosition.BOTTOM,
|
||||
margin: const EdgeInsets.all(16),
|
||||
);
|
||||
|
||||
// Navigate back to login
|
||||
Get.back();
|
||||
}
|
||||
} on FirebaseAuthException catch (e) {
|
||||
switch (e.code) {
|
||||
case 'email-already-in-use':
|
||||
errorMessage.value = 'Email sudah digunakan';
|
||||
break;
|
||||
case 'invalid-email':
|
||||
errorMessage.value = 'Format email tidak valid';
|
||||
break;
|
||||
case 'weak-password':
|
||||
errorMessage.value = 'Password terlalu lemah';
|
||||
break;
|
||||
default:
|
||||
errorMessage.value = 'Gagal mendaftar: ${e.message}';
|
||||
}
|
||||
} catch (e) {
|
||||
errorMessage.value = 'Terjadi kesalahan: ${e.toString()}';
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TeacherRegisterPage extends StatelessWidget {
|
||||
const TeacherRegisterPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(TeacherRegisterController());
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.blue.shade800,
|
||||
Colors.indigo.shade900,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Back button at the top
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 20),
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 16),
|
||||
backgroundColor: Colors.white.withOpacity(0.2),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: const [
|
||||
Icon(Icons.arrow_back, size: 18),
|
||||
SizedBox(width: 8),
|
||||
Text('Kembali ke Halaman Login'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// App Logo with Hero animation
|
||||
Hero(
|
||||
tag: 'app_logo',
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(25),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.2),
|
||||
blurRadius: 15,
|
||||
offset: const Offset(0, 8),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.psychology,
|
||||
size: 60,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 20),
|
||||
|
||||
// App Title
|
||||
const Text(
|
||||
'EduGuide',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 1.2,
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Register Subtitle
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Text(
|
||||
'Daftar Akun Guru',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Registration Card
|
||||
Obx(() => Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
blurRadius: 20,
|
||||
offset: const Offset(0, 10),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.app_registration_rounded,
|
||||
color: Colors.blue.shade600,
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Daftar Akun Baru',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text(
|
||||
'Lengkapi data diri Anda',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Name field
|
||||
CustomTextField(
|
||||
controller: controller.nameController,
|
||||
label: 'Nama Lengkap',
|
||||
prefixIcon: Icons.person_outline,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Email field
|
||||
CustomTextField(
|
||||
controller: controller.emailController,
|
||||
label: 'Email',
|
||||
prefixIcon: Icons.email_outlined,
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// School field
|
||||
CustomTextField(
|
||||
controller: controller.schoolController,
|
||||
label: 'Sekolah',
|
||||
prefixIcon: Icons.school_outlined,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Subject field
|
||||
CustomTextField(
|
||||
controller: controller.subjectController,
|
||||
label: 'Mata Pelajaran (Opsional)',
|
||||
prefixIcon: Icons.book_outlined,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Password field
|
||||
CustomTextField(
|
||||
controller: controller.passwordController,
|
||||
label: 'Password',
|
||||
prefixIcon: Icons.lock_outline,
|
||||
isPassword: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Confirm Password field
|
||||
CustomTextField(
|
||||
controller:
|
||||
controller.confirmPasswordController,
|
||||
label: 'Konfirmasi Password',
|
||||
prefixIcon: Icons.lock_outline,
|
||||
isPassword: true,
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Error message
|
||||
Obx(() => controller
|
||||
.errorMessage.value.isNotEmpty
|
||||
? Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius:
|
||||
BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: Colors.red.shade200,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.error_outline,
|
||||
color: Colors.red.shade800),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
controller.errorMessage.value,
|
||||
style: TextStyle(
|
||||
color: Colors.red.shade700,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Register button
|
||||
_buildPrimaryButton(
|
||||
label: 'Daftar',
|
||||
icon: Icons.how_to_reg,
|
||||
isLoading: controller.isLoading.value,
|
||||
onPressed: controller.registerTeacher,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Helper method to build text fields
|
||||
|
||||
// Helper method to build primary button
|
||||
Widget _buildPrimaryButton({
|
||||
required String label,
|
||||
required IconData icon,
|
||||
required VoidCallback onPressed,
|
||||
required bool isLoading,
|
||||
}) {
|
||||
return ElevatedButton(
|
||||
onPressed: isLoading ? null : onPressed,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade700,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
elevation: 0,
|
||||
disabledBackgroundColor: Colors.blue.shade300,
|
||||
),
|
||||
child: isLoading
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.5,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(icon, size: 20),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,693 @@
|
|||
// SplashScreen - Halaman pertama yang muncul dengan animasi dan cek sesi
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/page_student_dashboard.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SplashScreen extends StatefulWidget {
|
||||
const SplashScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<SplashScreen> createState() => _SplashScreenState();
|
||||
}
|
||||
|
||||
class _SplashScreenState extends State<SplashScreen>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _mainController;
|
||||
late AnimationController _networkController;
|
||||
late Animation<double> _fadeAnimation;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _translateAnimation;
|
||||
|
||||
// List of network nodes for neural network visualization
|
||||
final List<NetworkNode> _nodes = [];
|
||||
final List<NetworkConnection> _connections = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// Create network nodes and connections
|
||||
_setupNetworkGraph();
|
||||
|
||||
// Main controller for logo animations - 3 seconds
|
||||
_mainController = AnimationController(
|
||||
duration: const Duration(milliseconds: 3000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
// Separate slow controller for network animations - 15 seconds and repeat
|
||||
_networkController = AnimationController(
|
||||
duration: const Duration(milliseconds: 15000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
// Fade in animation for text
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _mainController,
|
||||
curve: const Interval(0.3, 0.8, curve: Curves.easeInOut),
|
||||
));
|
||||
|
||||
// Scale animation for logo - starts very small
|
||||
_scaleAnimation = Tween<double>(
|
||||
begin: 0.1,
|
||||
end: 1.0,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _mainController,
|
||||
curve: const Interval(0.1, 0.6, curve: Curves.elasticOut),
|
||||
));
|
||||
|
||||
// Translation animation - starts way below screen
|
||||
_translateAnimation = Tween<double>(
|
||||
begin: 300.0, // Start far below center
|
||||
end: 0.0, // End at center
|
||||
).animate(CurvedAnimation(
|
||||
parent: _mainController,
|
||||
curve: const Interval(0.1, 0.6, curve: Curves.easeOutCubic),
|
||||
));
|
||||
|
||||
// Start animations
|
||||
_mainController.forward();
|
||||
_networkController.repeat(); // Continuous very slow animation for network
|
||||
|
||||
// Check user session after animation completes
|
||||
Future.delayed(const Duration(milliseconds: 3500), () {
|
||||
checkExistingSession();
|
||||
});
|
||||
}
|
||||
|
||||
// Setup network graph with nodes and connections
|
||||
void _setupNetworkGraph() {
|
||||
final random = math.Random(42); // Fixed seed for consistent layout
|
||||
|
||||
// Create nodes - spreading them beyond screen boundaries
|
||||
for (int i = 0; i < 25; i++) {
|
||||
// Increased number of nodes
|
||||
_nodes.add(
|
||||
NetworkNode(
|
||||
x: -0.3 +
|
||||
random.nextDouble() *
|
||||
1.6, // Expand beyond screen boundaries (-0.3 to 1.3)
|
||||
y: -0.3 + random.nextDouble() * 1.6,
|
||||
size: 3.0 + random.nextDouble() * 5.0, // Slightly larger nodes
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Create connections between nodes - increased connections
|
||||
for (int i = 0; i < _nodes.length; i++) {
|
||||
// Each node connects to 3-6 other nodes
|
||||
final connectionCount = 3 + random.nextInt(4);
|
||||
final connectedIndices = <int>{};
|
||||
|
||||
for (int j = 0; j < connectionCount; j++) {
|
||||
// Try to find a new node to connect to
|
||||
int attempts = 0;
|
||||
while (attempts < 10) {
|
||||
final targetIndex = random.nextInt(_nodes.length);
|
||||
|
||||
// Don't connect to self and don't duplicate connections
|
||||
if (targetIndex != i && !connectedIndices.contains(targetIndex)) {
|
||||
connectedIndices.add(targetIndex);
|
||||
|
||||
// Add the connection
|
||||
_connections.add(
|
||||
NetworkConnection(
|
||||
sourceIndex: i,
|
||||
targetIndex: targetIndex,
|
||||
pulseOffset: random.nextDouble(),
|
||||
pulseSpeed:
|
||||
0.2 + random.nextDouble() * 0.3, // Slightly slower pulse
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> checkExistingSession() async {
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
|
||||
try {
|
||||
User? currentUser = _auth.currentUser;
|
||||
if (currentUser != null) {
|
||||
// Get the schoolId from shared preferences
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final schoolId = prefs.getString('school_id');
|
||||
|
||||
if (schoolId != null && schoolId.isNotEmpty) {
|
||||
// Check if user data exists in the specific school
|
||||
final docSnapshot = await _firestore
|
||||
.collection('schools')
|
||||
.doc(schoolId)
|
||||
.collection('students')
|
||||
.doc(currentUser.uid)
|
||||
.get();
|
||||
|
||||
if (docSnapshot.exists) {
|
||||
// Navigate to dashboard
|
||||
Get.offAll(() => const PageStudentDashboard());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If no school ID found or user not found in that school, search all schools
|
||||
final schoolsSnapshot = await _firestore.collection('schools').get();
|
||||
bool foundInAnySchool = false;
|
||||
|
||||
for (var schoolDoc in schoolsSnapshot.docs) {
|
||||
final studentDoc = await schoolDoc.reference
|
||||
.collection('students')
|
||||
.doc(currentUser.uid)
|
||||
.get();
|
||||
|
||||
if (studentDoc.exists) {
|
||||
// Found student in this school, save the school ID
|
||||
await prefs.setString('school_id', schoolDoc.id);
|
||||
foundInAnySchool = true;
|
||||
|
||||
// Navigate to dashboard
|
||||
Get.offAll(() => const PageStudentDashboard());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundInAnySchool) {
|
||||
// User authenticated but no profile found in any school
|
||||
Get.offAll(() => const IntroPage());
|
||||
}
|
||||
} else {
|
||||
// User not logged in, go to intro
|
||||
Get.offAll(() => const IntroPage());
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error checking session: $e');
|
||||
// If error, go to intro
|
||||
Get.offAll(() => const IntroPage());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_mainController.dispose();
|
||||
_networkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
// Beautiful gradient background
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.blue.shade800,
|
||||
Colors.indigo.shade900,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Stack(
|
||||
children: [
|
||||
// Grid background pattern
|
||||
AnimatedBuilder(
|
||||
animation: _networkController,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: GridPatternPainter(
|
||||
progress: _networkController.value,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Neural network background - using CustomPaint for better performance
|
||||
AnimatedBuilder(
|
||||
animation: _networkController,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: NetworkPainter(
|
||||
progress: _networkController.value,
|
||||
nodes: _nodes,
|
||||
connections: _connections,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Subtle wave pattern in background
|
||||
AnimatedBuilder(
|
||||
animation: _networkController,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: WavePatternPainter(
|
||||
progress: _networkController.value,
|
||||
),
|
||||
size: Size.infinite,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
// Main content - centered column
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// Logo with animation from bottom to center with scaling
|
||||
AnimatedBuilder(
|
||||
animation: _mainController,
|
||||
builder: (context, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, _translateAnimation.value),
|
||||
child: Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: 150,
|
||||
height: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.shade700.withOpacity(0.6),
|
||||
blurRadius: 20,
|
||||
spreadRadius: 5,
|
||||
offset: const Offset(0, 5),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Center(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// Brain icon with glow effect
|
||||
Container(
|
||||
width: 110,
|
||||
height: 110,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade100.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.psychology,
|
||||
size: 95,
|
||||
color: Colors.blue.shade700,
|
||||
),
|
||||
// Animated glow around the icon
|
||||
AnimatedBuilder(
|
||||
animation: _networkController,
|
||||
builder: (context, child) {
|
||||
return CustomPaint(
|
||||
painter: GlowingCirclePainter(
|
||||
progress: _networkController.value,
|
||||
color: Colors.blue.shade500,
|
||||
),
|
||||
size: const Size(120, 120),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// App title with fade animation
|
||||
FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Text(
|
||||
'EduGuide',
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
letterSpacing: 1.5,
|
||||
shadows: [
|
||||
Shadow(
|
||||
color: Colors.black54,
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Subtitle with fade animation
|
||||
FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Text(
|
||||
'Rekomendasi Minat Bakat Anda',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: Colors.white.withOpacity(0.8),
|
||||
letterSpacing: 1.0,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 40),
|
||||
|
||||
// Loading indicator with delayed fade
|
||||
AnimatedBuilder(
|
||||
animation: _mainController,
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: _mainController.value > 0.5
|
||||
? (_mainController.value - 0.5) * 2
|
||||
: 0,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white.withOpacity(0.9)),
|
||||
strokeWidth: 3,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Node in the network
|
||||
class NetworkNode {
|
||||
final double x; // Position (0-1)
|
||||
final double y;
|
||||
final double size;
|
||||
|
||||
NetworkNode({
|
||||
required this.x,
|
||||
required this.y,
|
||||
required this.size,
|
||||
});
|
||||
}
|
||||
|
||||
// Connection between nodes
|
||||
class NetworkConnection {
|
||||
final int sourceIndex;
|
||||
final int targetIndex;
|
||||
final double pulseOffset; // Random offset for animation
|
||||
final double pulseSpeed; // Speed of pulse animation
|
||||
|
||||
NetworkConnection({
|
||||
required this.sourceIndex,
|
||||
required this.targetIndex,
|
||||
required this.pulseOffset,
|
||||
required this.pulseSpeed,
|
||||
});
|
||||
}
|
||||
|
||||
// Neural network visualization
|
||||
class NetworkPainter extends CustomPainter {
|
||||
final double progress;
|
||||
final List<NetworkNode> nodes;
|
||||
final List<NetworkConnection> connections;
|
||||
|
||||
NetworkPainter({
|
||||
required this.progress,
|
||||
required this.nodes,
|
||||
required this.connections,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// Draw connections first (lines between nodes)
|
||||
for (final connection in connections) {
|
||||
final source = nodes[connection.sourceIndex];
|
||||
final target = nodes[connection.targetIndex];
|
||||
|
||||
final sourcePos = Offset(source.x * size.width, source.y * size.height);
|
||||
final targetPos = Offset(target.x * size.width, target.y * size.height);
|
||||
|
||||
// Calculate distance for line dashing
|
||||
final dx = targetPos.dx - sourcePos.dx;
|
||||
final dy = targetPos.dy - sourcePos.dy;
|
||||
final distance = math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
// Create a normalized direction vector
|
||||
final dirX = dx / distance;
|
||||
final dirY = dy / distance;
|
||||
|
||||
// Calculate pulse position based on progress
|
||||
final pulseProgress =
|
||||
(progress * connection.pulseSpeed + connection.pulseOffset) % 1.0;
|
||||
final pulsePos = Offset(
|
||||
sourcePos.dx + dx * pulseProgress,
|
||||
sourcePos.dy + dy * pulseProgress,
|
||||
);
|
||||
|
||||
// Draw the connection line
|
||||
final linePaint = Paint()
|
||||
..color = Colors.white.withOpacity(0.15)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.2;
|
||||
|
||||
canvas.drawLine(sourcePos, targetPos, linePaint);
|
||||
|
||||
// Draw pulse traveling along the connection
|
||||
final pulsePaint = Paint()
|
||||
..color = Colors.blue.shade100.withOpacity(0.5)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(pulsePos, 2.5, pulsePaint);
|
||||
|
||||
// Add a subtle glow around the pulse
|
||||
final glowPaint = Paint()
|
||||
..color = Colors.blue.shade100.withOpacity(0.2)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(pulsePos, 5.0, glowPaint);
|
||||
}
|
||||
|
||||
// Draw nodes
|
||||
for (final node in nodes) {
|
||||
final nodePos = Offset(node.x * size.width, node.y * size.height);
|
||||
|
||||
// Node glow (outer circle)
|
||||
final glowPaint = Paint()
|
||||
..color = Colors.blue.shade200.withOpacity(0.2)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(nodePos, node.size * 1.8, glowPaint);
|
||||
|
||||
// Node main circle
|
||||
final nodePaint = Paint()
|
||||
..color = Colors.white.withOpacity(0.7)
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(nodePos, node.size, nodePaint);
|
||||
|
||||
// Node inner circle
|
||||
final innerPaint = Paint()
|
||||
..color = Colors.blue.shade100
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
canvas.drawCircle(nodePos, node.size * 0.6, innerPaint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(NetworkPainter oldDelegate) {
|
||||
return oldDelegate.progress != progress;
|
||||
}
|
||||
}
|
||||
|
||||
// Glowing circle around the icon
|
||||
class GlowingCirclePainter extends CustomPainter {
|
||||
final double progress;
|
||||
final Color color;
|
||||
|
||||
GlowingCirclePainter({required this.progress, required this.color});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final center = Offset(size.width / 2, size.height / 2);
|
||||
|
||||
// Pulsing circular glow - very subtle
|
||||
final double pulseSize = 1.0 + math.sin(progress * math.pi) * 0.08;
|
||||
|
||||
// Draw multiple circles with diminishing opacity
|
||||
for (int i = 0; i < 3; i++) {
|
||||
final paint = Paint()
|
||||
..color = color.withOpacity(0.2 - (i * 0.05))
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0 - (i * 0.5);
|
||||
|
||||
// Each circle is larger than the previous
|
||||
canvas.drawCircle(
|
||||
center,
|
||||
(50 + i * 5) * pulseSize,
|
||||
paint,
|
||||
);
|
||||
}
|
||||
|
||||
// Draw spinning arc - very slow
|
||||
final spinnerPaint = Paint()
|
||||
..color = color.withOpacity(0.7)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 2.0;
|
||||
|
||||
// Rotation angle changes very slowly with progress
|
||||
final startAngle = progress * math.pi;
|
||||
const arcLength = math.pi * 1.2; // Longer arc
|
||||
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(center: center, radius: 55),
|
||||
startAngle,
|
||||
arcLength,
|
||||
false,
|
||||
spinnerPaint,
|
||||
);
|
||||
|
||||
// Second arc in opposite direction - even slower
|
||||
canvas.drawArc(
|
||||
Rect.fromCircle(center: center, radius: 45),
|
||||
-startAngle * 0.7,
|
||||
-arcLength * 0.8,
|
||||
false,
|
||||
spinnerPaint..strokeWidth = 1.5,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(GlowingCirclePainter oldDelegate) {
|
||||
return oldDelegate.progress != progress || oldDelegate.color != color;
|
||||
}
|
||||
}
|
||||
|
||||
// Subtle wave pattern in background
|
||||
class WavePatternPainter extends CustomPainter {
|
||||
final double progress;
|
||||
|
||||
WavePatternPainter({required this.progress});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
// Very subtle horizontal waves
|
||||
final wavePaint = Paint()
|
||||
..color = Colors.blue.shade200.withOpacity(0.03)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.0;
|
||||
|
||||
const waveCount = 4;
|
||||
final waveHeight = size.height / waveCount;
|
||||
|
||||
for (int i = 0; i < waveCount; i++) {
|
||||
final path = Path();
|
||||
final baseY = i * waveHeight;
|
||||
final amplitude = 8.0; // Reduced wave height
|
||||
|
||||
path.moveTo(0, baseY);
|
||||
|
||||
// Draw a smooth sine wave across the screen - very slow movement
|
||||
for (double x = 0; x <= size.width; x += 10) {
|
||||
// Very slow wave movement (reduced multiplier from 0.5 to 0.2)
|
||||
final waveY = baseY +
|
||||
math.sin((x / size.width * 4) + progress * math.pi * 0.2) *
|
||||
amplitude;
|
||||
path.lineTo(x, waveY);
|
||||
}
|
||||
|
||||
canvas.drawPath(path, wavePaint);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(WavePatternPainter oldDelegate) {
|
||||
return oldDelegate.progress != progress;
|
||||
}
|
||||
}
|
||||
|
||||
// Grid background pattern
|
||||
class GridPatternPainter extends CustomPainter {
|
||||
final double progress;
|
||||
|
||||
GridPatternPainter({required this.progress});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint = Paint()
|
||||
..color = Colors.white.withOpacity(0.05)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 0.8;
|
||||
|
||||
// Create animated grid pattern
|
||||
final spacing = 35.0;
|
||||
final xCount = (size.width / spacing).ceil() + 1;
|
||||
final yCount = (size.height / spacing).ceil() + 1;
|
||||
final offset = progress * spacing * 0.5; // Slow movement
|
||||
|
||||
// Horizontal lines
|
||||
for (int i = 0; i < yCount; i++) {
|
||||
final y = i * spacing - offset;
|
||||
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
|
||||
}
|
||||
|
||||
// Vertical lines
|
||||
for (int i = 0; i < xCount; i++) {
|
||||
final x = i * spacing - offset;
|
||||
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
|
||||
}
|
||||
|
||||
// Add some diagonal lines for more depth
|
||||
final diagonalPaint = Paint()
|
||||
..color = Colors.blue.shade100.withOpacity(0.04)
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 0.7;
|
||||
|
||||
final maxDim = math.max(size.width, size.height);
|
||||
final diagonalSpacing = 70.0;
|
||||
final diagCount = (maxDim / diagonalSpacing).ceil() * 2;
|
||||
final diagOffset = progress * diagonalSpacing * 0.3; // Very slow movement
|
||||
|
||||
for (int i = -diagCount; i < diagCount; i++) {
|
||||
final startX = i * diagonalSpacing + diagOffset;
|
||||
canvas.drawLine(
|
||||
Offset(startX, 0),
|
||||
Offset(startX + maxDim, maxDim),
|
||||
diagonalPaint,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(GridPatternPainter oldDelegate) {
|
||||
return oldDelegate.progress != progress;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import 'package:forward_chaining_man_app/app/controllers/developer_controller.dart';
|
||||
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||
import 'package:get/get_state_manager/src/simple/get_controllers.dart';
|
||||
|
||||
/// Controller untuk DeveloperModePage
|
||||
class DeveloperModeController extends GetxController {
|
||||
final RxBool isDeveloperMode = developerMode.obs;
|
||||
|
||||
void toggleDeveloperMode(bool value) {
|
||||
isDeveloperMode.value = value;
|
||||
developerMode = value;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,677 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' as rootBundle;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:forward_chaining_man_app/admin_mode.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/about/page_about.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/page_profile.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/controller/question_controller.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/page_question.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/page_select_major.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/widget/wave_clipper.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/feature/recomendation_screen/view/page_recomendation_screen.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/model/data_student.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:math' as math;
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
// Helper function to replace the simple dialog with our new UI
|
||||
void showRecommendationResultsGetx(RecommendationResult result,
|
||||
{String rawMessage = ''}) {
|
||||
Get.to(() => RecommendationResultsScreen(
|
||||
result: result,
|
||||
rawMessage: rawMessage,
|
||||
));
|
||||
}
|
||||
|
||||
class HomeController extends GetxController {
|
||||
final Rx<bool?> pilihan =
|
||||
Rx<bool?>(null); // null=belum pilih; true=Kerja; false=Kuliah
|
||||
final RxString selectedKode =
|
||||
"".obs; // Menyimpan kode pilihan yang dipilih user
|
||||
|
||||
// Step tracking - new variable to track current selection step
|
||||
final RxInt currentStep = 0.obs; // 0=Pilih kondisi ekonomi, 1=Pilih rencana
|
||||
|
||||
// Variable to track selected economic condition
|
||||
final RxString selectedEconomicCondition = "".obs; // "CUKUP" or "TERBATAS"
|
||||
|
||||
// Dapatkan preferensi minat dari controller sebelumnya
|
||||
final majorPrefController = Get.find<MajorPreferenceController>();
|
||||
|
||||
// Method to select economic condition in step 1
|
||||
void setEconomicCondition(String condition) {
|
||||
selectedEconomicCondition.value = condition;
|
||||
// Move to next step
|
||||
currentStep.value = 1;
|
||||
}
|
||||
|
||||
// Method to select plan in step 2
|
||||
void setPilihan(String kode) {
|
||||
if (selectedKode.value == kode)
|
||||
return; // Jika memilih yang sama, tidak berubah
|
||||
selectedKode.value = kode;
|
||||
|
||||
// Logika pemilihan: Kuliah atau Kerja
|
||||
if (kode == "E01" || kode == "E02" || kode == "E03") {
|
||||
pilihan.value = false; // Kuliah
|
||||
} else if (kode == "E04" || kode == "E05") {
|
||||
pilihan.value = true; // Kerja
|
||||
}
|
||||
}
|
||||
|
||||
// Method to go back to step 1
|
||||
void goBackToStep1() {
|
||||
currentStep.value = 0;
|
||||
selectedKode.value = "";
|
||||
pilihan.value = null;
|
||||
}
|
||||
|
||||
// Tambahkan getter untuk memudahkan akses preferensi minat
|
||||
String get selectedMajor => majorPrefController.selectedMajor.value;
|
||||
bool get isSainsMajor => selectedMajor == "SAINS";
|
||||
}
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(HomeController());
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.blue.shade800,
|
||||
Colors.indigo.shade900,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Custom App Bar
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
// If on step 2, go back to step 1, otherwise go back to previous page
|
||||
if (controller.currentStep.value == 1) {
|
||||
controller.goBackToStep1();
|
||||
} else {
|
||||
Get.back();
|
||||
}
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Obx(() => Text(
|
||||
controller.currentStep.value == 0
|
||||
? 'Pilih Kondisi Ekonomi'
|
||||
: 'Pilih Rencana Anda',
|
||||
style: const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Main Content Area
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 5),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(30),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
|
||||
child: Obx(() {
|
||||
if (controller.currentStep.value == 0) {
|
||||
// STEP 1: Choose Economic Condition
|
||||
return _buildEconomicConditionStep(controller);
|
||||
} else {
|
||||
// STEP 2: Choose Plan based on Economic Condition
|
||||
return _buildPlanSelectionStep(controller, context);
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Fungsi untuk menampilkan dialog konfirmasi
|
||||
void _showConfirmationDialog(BuildContext context, VoidCallback onConfirm) {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: 'Konfirmasi',
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return Container(); // Tidak digunakan, kita menggunakan transitionBuilder
|
||||
},
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
final curvedAnimation = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutBack,
|
||||
);
|
||||
|
||||
return ScaleTransition(
|
||||
scale: Tween<double>(begin: 0.8, end: 1.0).animate(curvedAnimation),
|
||||
child: FadeTransition(
|
||||
opacity:
|
||||
Tween<double>(begin: 0.0, end: 1.0).animate(curvedAnimation),
|
||||
child: Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: ConfirmationDialogContent(onConfirm: onConfirm),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// STEP 1: Widget for Economic Condition Selection
|
||||
Widget _buildEconomicConditionStep(HomeController controller) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.account_balance_wallet_outlined,
|
||||
size: 24,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Kondisi Ekonomi',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Pilih situasi yang paling sesuai',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// Economic Condition Cards
|
||||
buildEconomicConditionCard(
|
||||
title: "Kondisi ekonomi cukup untuk kuliah",
|
||||
subtitle: "Memiliki dana untuk pendidikan lanjutan",
|
||||
condition: "CUKUP",
|
||||
icon: Icons.school,
|
||||
controller: controller,
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
buildEconomicConditionCard(
|
||||
title: "Kondisi ekonomi terbatas",
|
||||
subtitle: "Perlu mempertimbangkan berbagai pilihan",
|
||||
condition: "TERBATAS",
|
||||
icon: Icons.attach_money,
|
||||
controller: controller,
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// STEP 2: Widget for Plan Selection based on Economic Condition
|
||||
Widget _buildPlanSelectionStep(
|
||||
HomeController controller, BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.lightbulb_outline,
|
||||
size: 24,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
controller.selectedEconomicCondition.value == "CUKUP"
|
||||
? 'Dengan Ekonomi Cukup'
|
||||
: 'Dengan Ekonomi Terbatas',
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Pilih rencana yang paling sesuai',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Options List
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
children: [
|
||||
// Show relevant options based on economic condition
|
||||
if (controller.selectedEconomicCondition.value == "CUKUP") ...[
|
||||
// Options for sufficient economic condition
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Kuliah",
|
||||
subtitle: "Melanjutkan pendidikan ke perguruan tinggi",
|
||||
kode: "E01",
|
||||
icon: Icons.school,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Mencari beasiswa",
|
||||
subtitle: "Kuliah dengan bantuan biaya pendidikan",
|
||||
kode: "E03",
|
||||
icon: Icons.card_giftcard,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Memilih bekerja atau usaha",
|
||||
subtitle: "Langsung terjun ke dunia kerja",
|
||||
kode: "E04",
|
||||
icon: Icons.work,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Bekerja dulu, kuliah nanti",
|
||||
subtitle: "Menunda kuliah untuk bekerja",
|
||||
kode: "E05",
|
||||
icon: Icons.timeline,
|
||||
controller: controller,
|
||||
)),
|
||||
] else ...[
|
||||
// Options for limited economic condition
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Mencari beasiswa",
|
||||
subtitle: "Kuliah dengan bantuan biaya pendidikan",
|
||||
kode: "E03",
|
||||
icon: Icons.card_giftcard,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Kuliah dengan biaya terjangkau",
|
||||
subtitle: "Memilih perguruan tinggi yang ekonomis",
|
||||
kode: "E02",
|
||||
icon: Icons.school,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Memilih bekerja atau usaha",
|
||||
subtitle: "Langsung terjun ke dunia kerja",
|
||||
kode: "E04",
|
||||
icon: Icons.work,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
Obx(() => buildOptionCard(
|
||||
title: "Bekerja dulu, kuliah nanti",
|
||||
subtitle: "Menunda kuliah untuk bekerja",
|
||||
kode: "E05",
|
||||
icon: Icons.timeline,
|
||||
controller: controller,
|
||||
)),
|
||||
],
|
||||
|
||||
// Add some space at the bottom for better scrolling
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Next Button
|
||||
Obx(() => AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
opacity: controller.selectedKode.value.isEmpty ? 0.6 : 1.0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow: controller.selectedKode.value.isEmpty
|
||||
? []
|
||||
: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.shade300.withOpacity(0.4),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed: controller.selectedKode.value.isEmpty
|
||||
? null
|
||||
: () {
|
||||
// Munculkan dialog konfirmasi terlebih dahulu
|
||||
_showConfirmationDialog(context, () {
|
||||
// Callback ini akan dipanggil ketika user menekan tombol "Ya, Lanjutkan"
|
||||
// Buat instance QuestionController dengan preferensi yang sesuai
|
||||
final questionController =
|
||||
Get.put(QuestionController(
|
||||
isKerja: controller.pilihan.value!,
|
||||
majorType: controller.selectedMajor,
|
||||
));
|
||||
|
||||
questionController.clearQuestion();
|
||||
questionController.loadProgramData(
|
||||
controller.pilihan.value!,
|
||||
controller.selectedMajor);
|
||||
|
||||
// Navigasi ke halaman pertanyaan
|
||||
Get.to(() => QuestionPage(
|
||||
isKerja: controller.pilihan.value!,
|
||||
));
|
||||
});
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
minimumSize: const Size(double.infinity, 54),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
elevation: 0,
|
||||
disabledBackgroundColor: Colors.blue.shade200,
|
||||
disabledForegroundColor: Colors.white70,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
controller.selectedKode.value.isEmpty
|
||||
? 'Pilih Salah Satu Opsi'
|
||||
: 'Lanjutkan',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.arrow_forward, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Widget for economic condition selection card (Step 1)
|
||||
Widget buildEconomicConditionCard({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required String condition,
|
||||
required IconData icon,
|
||||
required HomeController controller,
|
||||
}) {
|
||||
return Container(
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => controller.setEconomicCondition(condition),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Colors.white,
|
||||
border: Border.all(
|
||||
color: Colors.grey.shade200,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.shade200,
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 30,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: const TextStyle(
|
||||
fontSize: 17,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
color: Colors.blue.shade300,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Widget untuk membuat tampilan pilihan lebih menarik dan modern (Step 2)
|
||||
Widget buildOptionCard({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required String kode,
|
||||
required IconData icon,
|
||||
required HomeController controller,
|
||||
}) {
|
||||
final isSelected = controller.selectedKode.value == kode;
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => controller.setPilihan(kode),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: isSelected ? Colors.blue.shade50 : Colors.white,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue.shade400 : Colors.grey.shade200,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.shade200,
|
||||
blurRadius: isSelected ? 8 : 4,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Colors.blue.shade400
|
||||
: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 24,
|
||||
color: isSelected ? Colors.white : Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected
|
||||
? Colors.blue.shade800
|
||||
: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isSelected
|
||||
? Colors.blue.shade600
|
||||
: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
opacity: isSelected ? 1.0 : 0.0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade400,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.check,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,354 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/page_select_economy.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MajorPreferenceController extends GetxController {
|
||||
final RxString selectedMajor = "".obs;
|
||||
|
||||
void setMajor(String major) {
|
||||
if (selectedMajor.value == major) return;
|
||||
selectedMajor.value = major;
|
||||
}
|
||||
}
|
||||
|
||||
class MajorPreferencePage extends StatelessWidget {
|
||||
const MajorPreferencePage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(MajorPreferenceController());
|
||||
|
||||
return Scaffold(
|
||||
body: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.blue.shade800,
|
||||
Colors.indigo.shade900,
|
||||
],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// Custom App Bar
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () => Get.back(),
|
||||
child: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const Text(
|
||||
'Pilih Minat Anda',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Main Content Area
|
||||
Expanded(
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(top: 24),
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(30),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade50,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.school_outlined,
|
||||
size: 24,
|
||||
color: Colors.blue.shade800,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Minat IPA',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'Pilih bidang yang paling Anda minati',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Options List
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
children: [
|
||||
// IPA Sains Option
|
||||
Obx(() => buildMajorCard(
|
||||
title: "IPA (Sains Murni)",
|
||||
subtitle:
|
||||
"Fokus: Biologi, Kimia, Fisika (kedokteran, farmasi, sains)",
|
||||
description:
|
||||
"Pilihan yang tepat jika Anda tertarik pada ilmu-ilmu murni seperti biologi, kimia, dan fisika. Ideal untuk karir di bidang kedokteran, farmasi, penelitian ilmiah, atau bidang kesehatan lainnya.",
|
||||
kode: "SAINS",
|
||||
icon: Icons.biotech,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// IPA Teknik Option
|
||||
Obx(() => buildMajorCard(
|
||||
title: "IPA (Teknik)",
|
||||
subtitle:
|
||||
"Fokus: Matematika, Fisika, IT (arah teknik/teknologi)",
|
||||
description:
|
||||
"Cocok jika Anda memiliki minat kuat di bidang matematika, fisika terapan, dan teknologi informasi. Ideal untuk karir di bidang teknik, IT, rekayasa, atau arsitektur.",
|
||||
kode: "TEKNIK",
|
||||
icon: Icons.engineering,
|
||||
controller: controller,
|
||||
)),
|
||||
|
||||
// Add some space at the bottom for better scrolling
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Next Button
|
||||
Obx(() => AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
opacity: controller.selectedMajor.value.isEmpty
|
||||
? 0.6
|
||||
: 1.0,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
boxShadow:
|
||||
controller.selectedMajor.value.isEmpty
|
||||
? []
|
||||
: [
|
||||
BoxShadow(
|
||||
color: Colors.blue.shade300
|
||||
.withOpacity(0.4),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: ElevatedButton(
|
||||
onPressed:
|
||||
controller.selectedMajor.value.isEmpty
|
||||
? null
|
||||
: () {
|
||||
// Navigate to economic preference page
|
||||
Get.to(() => HomePage());
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 16),
|
||||
minimumSize: const Size(double.infinity, 54),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
elevation: 0,
|
||||
disabledBackgroundColor: Colors.blue.shade200,
|
||||
disabledForegroundColor: Colors.white70,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
controller.selectedMajor.value.isEmpty
|
||||
? 'Pilih Salah Satu Minat'
|
||||
: 'Lanjutkan',
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.5,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.arrow_forward, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Widget untuk membuat card pilihan minat
|
||||
Widget buildMajorCard({
|
||||
required String title,
|
||||
required String subtitle,
|
||||
required String description,
|
||||
required String kode,
|
||||
required IconData icon,
|
||||
required MajorPreferenceController controller,
|
||||
}) {
|
||||
final isSelected = controller.selectedMajor.value == kode;
|
||||
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () => controller.setMajor(kode),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: isSelected ? Colors.blue.shade50 : Colors.white,
|
||||
border: Border.all(
|
||||
color: isSelected ? Colors.blue.shade400 : Colors.grey.shade200,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.shade200,
|
||||
blurRadius: isSelected ? 8 : 4,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? Colors.blue.shade400
|
||||
: Colors.grey.shade100,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(
|
||||
icon,
|
||||
size: 24,
|
||||
color: isSelected ? Colors.white : Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: isSelected
|
||||
? Colors.blue.shade800
|
||||
: Colors.black87,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
subtitle,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isSelected
|
||||
? Colors.blue.shade600
|
||||
: Colors.grey.shade600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
opacity: isSelected ? 1.0 : 0.0,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade400,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.check,
|
||||
color: Colors.white,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
description,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color:
|
||||
isSelected ? Colors.blue.shade700 : Colors.grey.shade700,
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ShimmerEffect extends StatefulWidget {
|
||||
final Widget child;
|
||||
final Color baseColor;
|
||||
final Color highlightColor;
|
||||
final Duration duration;
|
||||
|
||||
const ShimmerEffect({
|
||||
Key? key,
|
||||
required this.child,
|
||||
this.baseColor = Colors.white54,
|
||||
this.highlightColor = Colors.white,
|
||||
this.duration = const Duration(seconds: 2),
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ShimmerEffect> createState() => _ShimmerEffectState();
|
||||
}
|
||||
|
||||
class _ShimmerEffectState extends State<ShimmerEffect>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: widget.duration,
|
||||
)..repeat();
|
||||
|
||||
_animation = Tween<double>(begin: -1.0, end: 2.0).animate(
|
||||
CurvedAnimation(parent: _controller, curve: Curves.easeInOutSine),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (BuildContext context, Widget? child) {
|
||||
return ShaderMask(
|
||||
blendMode: BlendMode.srcIn,
|
||||
shaderCallback: (bounds) {
|
||||
return LinearGradient(
|
||||
colors: [
|
||||
widget.baseColor,
|
||||
widget.highlightColor,
|
||||
widget.baseColor,
|
||||
],
|
||||
stops: const [0.0, 0.5, 1.0],
|
||||
begin: Alignment.centerLeft,
|
||||
end: Alignment.centerRight,
|
||||
transform: _SlidingGradientTransform(
|
||||
slidePercent: _animation.value,
|
||||
),
|
||||
).createShader(bounds);
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SlidingGradientTransform extends GradientTransform {
|
||||
const _SlidingGradientTransform({
|
||||
required this.slidePercent,
|
||||
});
|
||||
|
||||
final double slidePercent;
|
||||
|
||||
@override
|
||||
Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
|
||||
return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,378 @@
|
|||
// Widget untuk animasi gelombang di bagian bawah dialog
|
||||
import 'dart:ui';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'dart:math' as math;
|
||||
|
||||
// Fungsi untuk menampilkan dialog konfirmasi
|
||||
void _showConfirmationDialog(BuildContext context, VoidCallback onConfirm) {
|
||||
showGeneralDialog(
|
||||
context: context,
|
||||
barrierDismissible: true,
|
||||
barrierLabel: 'Konfirmasi',
|
||||
transitionDuration: const Duration(milliseconds: 300),
|
||||
pageBuilder: (context, animation, secondaryAnimation) {
|
||||
return Container(); // Tidak digunakan, kita menggunakan transitionBuilder
|
||||
},
|
||||
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||
final curvedAnimation = CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeOutBack,
|
||||
);
|
||||
|
||||
return ScaleTransition(
|
||||
scale: Tween<double>(begin: 0.8, end: 1.0).animate(curvedAnimation),
|
||||
child: FadeTransition(
|
||||
opacity: Tween<double>(begin: 0.0, end: 1.0).animate(curvedAnimation),
|
||||
child: Dialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
insetPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: ConfirmationDialogContent(onConfirm: onConfirm),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Widget konten dialog
|
||||
class ConfirmationDialogContent extends StatefulWidget {
|
||||
final VoidCallback onConfirm;
|
||||
|
||||
const ConfirmationDialogContent({
|
||||
Key? key,
|
||||
required this.onConfirm,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ConfirmationDialogContent> createState() =>
|
||||
_ConfirmationDialogContentState();
|
||||
}
|
||||
|
||||
class _ConfirmationDialogContentState extends State<ConfirmationDialogContent>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _waveController;
|
||||
final List<bool> _checkedItems = [false, false, false];
|
||||
bool get _allChecked => _checkedItems.every((element) => element);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_waveController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(seconds: 2),
|
||||
)..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_waveController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: 400,
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||
),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
// Background dengan gradien dan gelombang
|
||||
Positioned.fill(
|
||||
child: AnimatedBuilder(
|
||||
animation: _waveController,
|
||||
builder: (context, child) {
|
||||
return ClipPath(
|
||||
clipper: WaveClipper(_waveController.value),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Colors.blue.shade50,
|
||||
Colors.blue.shade100,
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
// Konten utama
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Header
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade600,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(20),
|
||||
topRight: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.check_circle,
|
||||
color: Colors.white,
|
||||
size: 48,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
'Konfirmasi Pemilihan',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'Pastikan pilihan Anda sudah sesuai sebelum melanjutkan ke kuisioner',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.white.withOpacity(0.9),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Checklist konfirmasi
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Silakan konfirmasi bahwa Anda:',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Checklist pertama
|
||||
CheckboxListTile(
|
||||
value: _checkedItems[0],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_checkedItems[0] = value ?? false;
|
||||
});
|
||||
},
|
||||
title: const Text(
|
||||
'Telah memilih minat yang benar-benar sesuai dengan diri Anda',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
activeColor: Colors.blue.shade600,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
dense: true,
|
||||
),
|
||||
|
||||
// Checklist kedua
|
||||
CheckboxListTile(
|
||||
value: _checkedItems[1],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_checkedItems[1] = value ?? false;
|
||||
});
|
||||
},
|
||||
title: const Text(
|
||||
'Mempertimbangkan kondisi ekonomi keluarga dalam membuat pilihan',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
activeColor: Colors.blue.shade600,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
dense: true,
|
||||
),
|
||||
|
||||
// Checklist ketiga
|
||||
CheckboxListTile(
|
||||
value: _checkedItems[2],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_checkedItems[2] = value ?? false;
|
||||
});
|
||||
},
|
||||
title: const Text(
|
||||
'Memiliki rencana yang jelas untuk masa depan Anda',
|
||||
style: TextStyle(fontSize: 14),
|
||||
),
|
||||
activeColor: Colors.blue.shade600,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
dense: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Pesan tentang kejujuran
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.amber.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.amber.shade200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
color: Colors.amber.shade800,
|
||||
size: 24,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
'Di halaman selanjutnya Anda akan menjawab beberapa pertanyaan. Pastikan untuk menjawab dengan jujur sesuai kondisi Anda.',
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Tombol-tombol
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
|
||||
child: Row(
|
||||
children: [
|
||||
// Tombol Kembali
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: Colors.blue.shade700,
|
||||
side: BorderSide(color: Colors.blue.shade300),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
),
|
||||
child: const Text('Kembali'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
|
||||
// Tombol Lanjutkan
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: ElevatedButton(
|
||||
onPressed: _allChecked
|
||||
? () {
|
||||
Navigator.of(context).pop();
|
||||
widget.onConfirm();
|
||||
}
|
||||
: null,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.blue.shade600,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
),
|
||||
),
|
||||
child: const Text(
|
||||
'Ya, Lanjutkan',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// Lingkaran dekoratif di pojok
|
||||
Positioned(
|
||||
right: -15,
|
||||
top: -15,
|
||||
child: Container(
|
||||
height: 60,
|
||||
width: 60,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade400.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: -10,
|
||||
bottom: -10,
|
||||
child: Container(
|
||||
height: 40,
|
||||
width: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.shade400.withOpacity(0.3),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WaveClipper extends CustomClipper<Path> {
|
||||
final double animation;
|
||||
|
||||
WaveClipper(this.animation);
|
||||
|
||||
@override
|
||||
Path getClip(Size size) {
|
||||
final path = Path();
|
||||
final height = size.height;
|
||||
final width = size.width;
|
||||
|
||||
path.lineTo(0, height * 0.7);
|
||||
|
||||
// Buat gelombang dengan animasi
|
||||
final firstControlPoint = Offset(
|
||||
width * 0.25, height * (0.7 + 0.04 * math.sin(animation * math.pi)));
|
||||
final firstEndPoint = Offset(width * 0.5, height * 0.7);
|
||||
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
|
||||
firstEndPoint.dx, firstEndPoint.dy);
|
||||
|
||||
final secondControlPoint = Offset(
|
||||
width * 0.75, height * (0.7 - 0.04 * math.sin(animation * math.pi)));
|
||||
final secondEndPoint = Offset(width, height * 0.7);
|
||||
path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
|
||||
secondEndPoint.dx, secondEndPoint.dy);
|
||||
|
||||
path.lineTo(width, 0);
|
||||
path.close();
|
||||
return path;
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(covariant CustomClipper oldClipper) => true;
|
||||
}
|