feat: init project

This commit is contained in:
Yanuar Tr 2025-06-19 20:25:09 +07:00
commit 653cee119d
177 changed files with 45797 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# forward_chaining_man_app
A new Flutter project.

1
analysis_options.yaml Normal file
View File

@ -0,0 +1 @@
include: package:flutter_lints/flutter.yaml

13
android/.gitignore vendored Normal file
View File

@ -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

47
android/app/build.gradle Normal file
View File

@ -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 = "../.."
}

View File

@ -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"
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,5 @@
package com.example.forward_chaining_man_app
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -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>

View File

@ -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>

View File

@ -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>

18
android/build.gradle Normal file
View File

@ -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
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true

View File

@ -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

28
android/settings.gradle Normal file
View File

@ -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"

View File

@ -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"
}
]

View File

@ -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"
}
]

184
assets/data_beasiswa.json Normal file
View File

@ -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/"
}
]
}

20
assets/data_ekonomi.json Normal file
View File

@ -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"
}
]
}

BIN
assets/ic_denis.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
assets/ic_google.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

734
assets/ipa_sains_kerja.json Normal file
View File

@ -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"
}
]
}
}

1046
assets/ipa_sains_kuliah.json Normal file

File diff suppressed because it is too large Load Diff

1181
assets/ipa_teknik_kerja.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

BIN
assets/profile_dev.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

3
devtools_options.yaml Normal file
View File

@ -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:

1
firebase.json Normal file
View File

@ -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"}}}}}

34
ios/.gitignore vendored Normal file
View File

@ -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

View File

@ -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>

View File

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View File

@ -0,0 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

59
ios/Podfile Normal file
View File

@ -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

1531
ios/Podfile.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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 */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -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.

View File

@ -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>

View File

@ -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>

View File

@ -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>

69
ios/Runner/Info.plist Normal file
View File

@ -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>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -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.
}
}

139
lib/admin_mode.dart Normal file
View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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,
),
),
);
}
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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 '-';
}
}

View File

@ -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,
),
),
],
),
);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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,
),
),
),
],
),
),
),
),
);
}
}

View File

@ -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,
),
),
],
),
),
),
);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More