feat: init project
|
@ -0,0 +1 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
|
@ -0,0 +1,13 @@
|
||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/to/reference-keystore
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
|
@ -0,0 +1,47 @@
|
||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
// START: FlutterFire Configuration
|
||||||
|
id 'com.google.gms.google-services'
|
||||||
|
// END: FlutterFire Configuration
|
||||||
|
id "kotlin-android"
|
||||||
|
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.example.forward_chaining_man_app"
|
||||||
|
compileSdk = 34
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId = "com.example.forward_chaining_man_app"
|
||||||
|
// You can update the following values to match your application needs.
|
||||||
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
|
minSdk = 23
|
||||||
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
versionCode = flutter.versionCode
|
||||||
|
versionName = flutter.versionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig = signingConfigs.debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
{
|
||||||
|
"project_info": {
|
||||||
|
"project_number": "1072369607324",
|
||||||
|
"project_id": "sirekomendasi-dc7de",
|
||||||
|
"storage_bucket": "sirekomendasi-dc7de.firebasestorage.app"
|
||||||
|
},
|
||||||
|
"client": [
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:1072369607324:android:f76a929d6f6258726f4359",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-pnvpbn64ceilqdub09qfj2h6re5199tj.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.e",
|
||||||
|
"certificate_hash": "54c882833621e66da646ffb2d8ee7237fa7f0a24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-t2qd5p0r7lviqemnvjidshbud49es45l.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.e",
|
||||||
|
"certificate_hash": "3bb526cd960a2c584c325eb17221250f321e34d0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyBIHdD_rH3YSfuZYcNKFd1YCMTXxYb6gdc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5.apps.googleusercontent.com",
|
||||||
|
"client_type": 2,
|
||||||
|
"ios_info": {
|
||||||
|
"bundle_id": "com.example.forwardChainingManApp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_info": {
|
||||||
|
"mobilesdk_app_id": "1:1072369607324:android:ed7767d8870c32e96f4359",
|
||||||
|
"android_client_info": {
|
||||||
|
"package_name": "com.example.forward_chaining_man_app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-hp2vujt95ipkft4n348a96ndbiocqrdo.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.example.forward_chaining_man_app",
|
||||||
|
"certificate_hash": "54c882833621e66da646ffb2d8ee7237fa7f0a24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-pbgftae664k56gli4fb0ftrhot88t8qi.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.example.forward_chaining_man_app",
|
||||||
|
"certificate_hash": "3bb526cd960a2c584c325eb17221250f321e34d0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
{
|
||||||
|
"current_key": "AIzaSyBIHdD_rH3YSfuZYcNKFd1YCMTXxYb6gdc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": {
|
||||||
|
"appinvite_service": {
|
||||||
|
"other_platform_oauth_client": [
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-ul5feal98a8o8g9fo23ddp7ob886ermd.apps.googleusercontent.com",
|
||||||
|
"client_type": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5.apps.googleusercontent.com",
|
||||||
|
"client_type": 2,
|
||||||
|
"ios_info": {
|
||||||
|
"bundle_id": "com.example.forwardChainingManApp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration_version": "1"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,49 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:label="Edu Guide"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<!-- Required to query activities that can process text, see:
|
||||||
|
https://developer.android.com/training/package-visibility and
|
||||||
|
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||||
|
|
||||||
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.example.forward_chaining_man_app
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity()
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 42 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
the Flutter engine draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,18 @@
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = "../build"
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
|
@ -0,0 +1,5 @@
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
|
|
@ -0,0 +1,28 @@
|
||||||
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}()
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
|
id "com.android.application" version "8.1.0" apply false
|
||||||
|
// START: FlutterFire Configuration
|
||||||
|
id "com.google.gms.google-services" version "4.3.15" apply false
|
||||||
|
// END: FlutterFire Configuration
|
||||||
|
id "org.jetbrains.kotlin.android" version "1.9.24" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
|
@ -0,0 +1,279 @@
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"NO": "",
|
||||||
|
"PROGRAM": "",
|
||||||
|
"NAMA PROGRAM STUDI": "",
|
||||||
|
"BKT PER SEMESTER": "",
|
||||||
|
"UKT_KELOMPOK_1": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||||
|
"UKT_KELOMPOK_2": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||||
|
"UKT_KELOMPOK_3": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI 75%",
|
||||||
|
"UKT_KELOMPOK_4": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI 50%",
|
||||||
|
"UKT_KELOMPOK_5": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI 25%",
|
||||||
|
"UKT_KELOMPOK_6": "UKT_PENDIDIKAN_UNGGUL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "1",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Perangkat Lunak",
|
||||||
|
"BKT PER SEMESTER": "28.008.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "2",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Mesin",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "3",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Elektro",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "4",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Instrumentasi dan Kontrol",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "5",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Survei dan Pemetaan Dasar",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "6",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Sistem Informasi Geografis",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "7",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Pengembangan Produk Agroindustri",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "8",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Pengelolaan dan Perawatan Alat Berat",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "9",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Internet",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "10",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Pengelolaan dan Pemeliharaan Infrastruktur Sipil",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "11",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Rekayasa Pelaksanaan Bangunan Sipil",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "12",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Veteriner",
|
||||||
|
"BKT PER SEMESTER": "27.128.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "13",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Manajemen Informasi Kesehatan",
|
||||||
|
"BKT PER SEMESTER": "25.038.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "14",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Pengelolaan Hutan",
|
||||||
|
"BKT PER SEMESTER": "23.995.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "15",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Bisnis Perjalanan Wisata",
|
||||||
|
"BKT PER SEMESTER": "23.467.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "16",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Manajemen dan Penilaian Properti",
|
||||||
|
"BKT PER SEMESTER": "13.826.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "17",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Pengelolaan Arsip dan Rekaman Informasi",
|
||||||
|
"BKT PER SEMESTER": "13.298.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "18",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Bahasa Inggris",
|
||||||
|
"BKT PER SEMESTER": "13.298.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "19",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Bahasa Jepang untuk Komunikasi Bisnis dan Profesional",
|
||||||
|
"BKT PER SEMESTER": "13.298.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "20",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Akuntansi Sektor Publik",
|
||||||
|
"BKT PER SEMESTER": "13.298.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "21",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Pembangunan Ekonomi Kewilayahan",
|
||||||
|
"BKT PER SEMESTER": "13.298.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "22",
|
||||||
|
"PROGRAM": "D4",
|
||||||
|
"NAMA PROGRAM STUDI": "Perbankan",
|
||||||
|
"BKT PER SEMESTER": "13.298.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.850.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.550.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.400.000"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,866 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"NO": "",
|
||||||
|
"PROGRAM": "",
|
||||||
|
"NAMA PROGRAM STUDI": "",
|
||||||
|
"BKT PER SEMESTER": "",
|
||||||
|
"UKT_KELOMPOK_1": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||||
|
"UKT_KELOMPOK_2": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_100%",
|
||||||
|
"UKT_KELOMPOK_3": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_75%",
|
||||||
|
"UKT_KELOMPOK_4": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_50%",
|
||||||
|
"UKT_KELOMPOK_5": "UKT_PENDIDIKAN_UNGGUL_BERSUBSIDI_25%",
|
||||||
|
"UKT_KELOMPOK_6": "UKT_PENDIDIKAN_UNGGUL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "1",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Kedokteran",
|
||||||
|
"BKT PER SEMESTER": "35.145.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "6.175.000",
|
||||||
|
"UKT_KELOMPOK_4": "12.350.000",
|
||||||
|
"UKT_KELOMPOK_5": "18.525.000",
|
||||||
|
"UKT_KELOMPOK_6": "24.700.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "2",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Kedokteran Hewan",
|
||||||
|
"BKT PER SEMESTER": "35.145.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "6.175.000",
|
||||||
|
"UKT_KELOMPOK_4": "12.350.000",
|
||||||
|
"UKT_KELOMPOK_5": "18.525.000",
|
||||||
|
"UKT_KELOMPOK_6": "24.700.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "3",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Kedokteran Gigi",
|
||||||
|
"BKT PER SEMESTER": "33.740.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "6.175.000",
|
||||||
|
"UKT_KELOMPOK_4": "12.350.000",
|
||||||
|
"UKT_KELOMPOK_5": "18.525.000",
|
||||||
|
"UKT_KELOMPOK_6": "24.700.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "4",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Farmasi",
|
||||||
|
"BKT PER SEMESTER": "27.919.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "4.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "8.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "12.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "17.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "5",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu Keperawatan",
|
||||||
|
"BKT PER SEMESTER": "26.802.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "4.325.000",
|
||||||
|
"UKT_KELOMPOK_4": "8.650.000",
|
||||||
|
"UKT_KELOMPOK_5": "12.975.000",
|
||||||
|
"UKT_KELOMPOK_6": "17.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "6",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Mesin",
|
||||||
|
"BKT PER SEMESTER": "20.949.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.375.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.750.000",
|
||||||
|
"UKT_KELOMPOK_5": "10.125.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.500.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "7",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Nuklir",
|
||||||
|
"BKT PER SEMESTER": "20.949.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "8",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Psikologi",
|
||||||
|
"BKT PER SEMESTER": "20.692.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "9",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Geologi",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "10",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Hasil Perikanan",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "11",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Perencanaan Wilayah dan Kota",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "12",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Arsitektur",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "13",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Geodesi",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "14",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Kimia",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.375.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.750.000",
|
||||||
|
"UKT_KELOMPOK_5": "10.125.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.500.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "15",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Elektro",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "16",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Industri",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "17",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Industri Pertanian",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "18",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Fisika",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "19",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Sipil",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "20",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Elektronika dan Instrumentasi",
|
||||||
|
"BKT PER SEMESTER": "19.908.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "21",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Gizi",
|
||||||
|
"BKT PER SEMESTER": "19.865.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "22",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Biomedis",
|
||||||
|
"BKT PER SEMESTER": "19.273.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "23",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Pertanian",
|
||||||
|
"BKT PER SEMESTER": "19.112.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "24",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Pangan dan Hasil Pertanian",
|
||||||
|
"BKT PER SEMESTER": "19.112.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "25",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Higiene Gigi",
|
||||||
|
"BKT PER SEMESTER": "19.037.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.375.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.750.000",
|
||||||
|
"UKT_KELOMPOK_5": "10.125.000",
|
||||||
|
"UKT_KELOMPOK_6": "13.500.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "26",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Biologi",
|
||||||
|
"BKT PER SEMESTER": "18.399.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "27",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Kimia",
|
||||||
|
"BKT PER SEMESTER": "18.399.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "28",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Infrastruktur Lingkungan",
|
||||||
|
"BKT PER SEMESTER": "18.315.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "29",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknik Sumber Daya Air",
|
||||||
|
"BKT PER SEMESTER": "18.315.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "30",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Geofisika",
|
||||||
|
"BKT PER SEMESTER": "17.663.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "31",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Fisika",
|
||||||
|
"BKT PER SEMESTER": "17.663.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "32",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Mikrobiologi Pertanian",
|
||||||
|
"BKT PER SEMESTER": "15.335.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "33",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Kehutanan",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "34",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu dan Industri Peternakan",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.750.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.500.000",
|
||||||
|
"UKT_KELOMPOK_5": "8.250.000",
|
||||||
|
"UKT_KELOMPOK_6": "11.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "35",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Agronomi",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "36",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Akuakultur",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "37",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ekonomi Pertanian dan Agribisnis",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "38",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu Tanah",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "39",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Manajemen Sumberdaya Akuatik",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "40",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Penyuluhan dan Komunikasi Pertanian",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "41",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Proteksi Tanaman",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "42",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Teknologi Informasi",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "3.075.000",
|
||||||
|
"UKT_KELOMPOK_4": "6.150.000",
|
||||||
|
"UKT_KELOMPOK_5": "9.225.000",
|
||||||
|
"UKT_KELOMPOK_6": "12.300.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "43",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Geografi Lingkungan",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "44",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Kartografi dan Penginderaan Jauh",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "45",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Akuntansi",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.300.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.600.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.900.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.200.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "46",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Manajemen",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.300.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.600.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.900.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.200.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "47",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Hukum",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.300.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.600.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.900.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.200.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "48",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu Komputer",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "49",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu Ekonomi",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.300.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.600.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.900.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.200.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "50",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Manajemen dan Kebijakan Publik",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.300.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.600.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.900.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.200.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "51",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu Hubungan Internasional",
|
||||||
|
"BKT PER SEMESTER": "14.667.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.200.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.400.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.600.000",
|
||||||
|
"UKT_KELOMPOK_6": "8.800.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "52",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu Komunikasi",
|
||||||
|
"BKT PER SEMESTER": "14.081.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.300.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.600.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.900.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.200.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "53",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Pembangunan Sosial dan Kesejahteraan",
|
||||||
|
"BKT PER SEMESTER": "14.081.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.200.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.400.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.600.000",
|
||||||
|
"UKT_KELOMPOK_6": "8.800.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "54",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Pariwisata",
|
||||||
|
"BKT PER SEMESTER": "14.081.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.900.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.800.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.600.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "55",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Statistika",
|
||||||
|
"BKT PER SEMESTER": "13.360.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "56",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Matematika",
|
||||||
|
"BKT PER SEMESTER": "13.360.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "57",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Ilmu Aktuaria",
|
||||||
|
"BKT PER SEMESTER": "12.292.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.500.000",
|
||||||
|
"UKT_KELOMPOK_4": "5.000.000",
|
||||||
|
"UKT_KELOMPOK_5": "7.500.000",
|
||||||
|
"UKT_KELOMPOK_6": "10.000.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "58",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Arkeologi",
|
||||||
|
"BKT PER SEMESTER": "9.442.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.300.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.600.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.900.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.200.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "59",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Pembangunan Wilayah",
|
||||||
|
"BKT PER SEMESTER": "9.025.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.256.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.512.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.768.000",
|
||||||
|
"UKT_KELOMPOK_6": "9.025.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "60",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Bahasa dan Sastra Prancis",
|
||||||
|
"BKT PER SEMESTER": "8.664.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.900.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.800.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.600.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "61",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Sastra Arab",
|
||||||
|
"BKT PER SEMESTER": "8.664.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.900.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.800.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.600.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "62",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Bahasa dan Sastra Indonesia",
|
||||||
|
"BKT PER SEMESTER": "8.664.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.900.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.800.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.600.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "63",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Bahasa dan Kebudayaan Korea",
|
||||||
|
"BKT PER SEMESTER": "8.664.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.900.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.800.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.600.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "64",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Bahasa dan Kebudayaan Jepang",
|
||||||
|
"BKT PER SEMESTER": "8.664.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.900.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.800.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.600.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "65",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Bahasa, Sastra, dan Budaya Jawa",
|
||||||
|
"BKT PER SEMESTER": "8.664.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.900.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.800.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.700.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.600.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "66",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Sastra Inggris",
|
||||||
|
"BKT PER SEMESTER": "8.664.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.200.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.400.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.600.000",
|
||||||
|
"UKT_KELOMPOK_6": "8.664.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "67",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Politik dan Pemerintahan",
|
||||||
|
"BKT PER SEMESTER": "8.214.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.053.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.107.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.160.000",
|
||||||
|
"UKT_KELOMPOK_6": "8.214.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "68",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Antropologi Budaya",
|
||||||
|
"BKT PER SEMESTER": "7.885.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.053.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.107.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.160.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.885.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "69",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Sejarah",
|
||||||
|
"BKT PER SEMESTER": "7.885.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "2.053.000",
|
||||||
|
"UKT_KELOMPOK_4": "4.107.000",
|
||||||
|
"UKT_KELOMPOK_5": "6.160.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.885.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "70",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Sosiologi",
|
||||||
|
"BKT PER SEMESTER": "7.885.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.889.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.778.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.667.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.557.000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NO": "71",
|
||||||
|
"PROGRAM": "S1",
|
||||||
|
"NAMA PROGRAM STUDI": "Filsafat",
|
||||||
|
"BKT PER SEMESTER": "7.885.000",
|
||||||
|
"UKT_KELOMPOK_1": "0",
|
||||||
|
"UKT_KELOMPOK_2": "0",
|
||||||
|
"UKT_KELOMPOK_3": "1.889.000",
|
||||||
|
"UKT_KELOMPOK_4": "3.778.000",
|
||||||
|
"UKT_KELOMPOK_5": "5.667.000",
|
||||||
|
"UKT_KELOMPOK_6": "7.557.000"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,184 @@
|
||||||
|
{
|
||||||
|
"beasiswa": [
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Indonesia Maju (BIM)",
|
||||||
|
"penyelenggara": "Kementerian Pendidikan, Kebudayaan, Riset, dan Teknologi",
|
||||||
|
"jenjang": ["S1", "S2"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Beasiswa bergelar (S1 dan S2) serta beasiswa non-gelar (Program Persiapan S1 Luar Negeri).",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0",
|
||||||
|
"Lolos seleksi administrasi, substansi, dan wawancara"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://bim-pusatprestasinasional.kemdikbud.go.id/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa KIP Kuliah",
|
||||||
|
"penyelenggara": "Kementerian Pendidikan, Kebudayaan, Riset, dan Teknologi",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Bantuan biaya pendidikan dan biaya hidup bagi mahasiswa dari keluarga kurang mampu.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki KIP",
|
||||||
|
"Lolos seleksi administrasi"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://kip-kuliah.kemdikbud.go.id/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa LPDP (Lembaga Pengelola Dana Pendidikan)",
|
||||||
|
"penyelenggara": "Kementerian Keuangan RI",
|
||||||
|
"jenjang": ["S1", "S2", "S3"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Beasiswa penuh yang mencakup biaya pendidikan dan biaya hidup untuk pendidikan dalam dan luar negeri.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0 untuk S1, 3.25 untuk S2/S3",
|
||||||
|
"Usia maksimal 21 tahun untuk S1, 35 tahun untuk S2, 40 tahun untuk S3",
|
||||||
|
"Lolos seleksi administrasi, substansi, dan wawancara"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka 2-3 kali setahun",
|
||||||
|
"link": "https://www.lpdp.kemenkeu.go.id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa S1 Glow and Lovely Bintang Beasiswa",
|
||||||
|
"penyelenggara": "Glow and Lovely",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Beasiswa penuh untuk program S1 di bidang yang relevan.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki prestasi akademik yang baik"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://www.glowandlovely.in"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Bank Indonesia",
|
||||||
|
"penyelenggara": "Bank Indonesia",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Ekonomi", "Keuangan", "Pendidikan"],
|
||||||
|
"deskripsi": "Bantuan dana pendidikan bulanan, pelatihan soft skill dan hard skill, serta workshop.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0",
|
||||||
|
"Lolos seleksi administrasi dan wawancara"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://www.kompas.com/edu/read/2025/01/27/125955371/beasiswa-bank-indonesia-2025-dibuka-cek-syarat-dan-cara-daftar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Van Deventer-Maas Indonesia (VDMI)",
|
||||||
|
"penyelenggara": "VDMI",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Bantuan dana bulanan, bonus lulus tepat waktu, bonus TOEFL, serta pelatihan pengembangan kapasitas pribadi.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki prestasi akademik yang baik",
|
||||||
|
"Lolos seleksi administrasi dan wawancara"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://beasiswapascasarjana.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Dataprint",
|
||||||
|
"penyelenggara": "Dataprint",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Bantuan dana sebesar Rp500.000 yang diberikan satu kali.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki prestasi akademik yang baik"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://indbeasiswa.com/beasiswa-dataprint/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Pertamina Sobat Bumi",
|
||||||
|
"penyelenggara": "Pertamina",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Dana hidup bulanan dan dana program pengabdian untuk desa.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0",
|
||||||
|
"Lolos seleksi administrasi"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://indbeasiswa.com/beasiswa-pertamina-sobat-bumi/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Karya Salemba Empat (KSE)",
|
||||||
|
"penyelenggara": "Karya Salemba Empat",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Bantuan dana pendidikan dan pengembangan diri.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0",
|
||||||
|
"Lolos seleksi administrasi"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://indbeasiswa.com/beasiswa-karya-salemba-empat/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Djarum Plus",
|
||||||
|
"penyelenggara": "Djarum Foundation",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Bantuan dana bulanan dan program pengembangan diri.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki prestasi akademik yang baik"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://indbeasiswa.com/beasiswa-djarum-plus/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Tanoto Foundation",
|
||||||
|
"penyelenggara": "Tanoto Foundation",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Semua Bidang Studi"],
|
||||||
|
"deskripsi": "Bantuan biaya pendidikan dan biaya hidup.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0",
|
||||||
|
"Lolos seleksi administrasi dan wawancara"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://indbeasiswa.com/beasiswa-tanoto-foundation/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa Bakti BCA",
|
||||||
|
"penyelenggara": "BCA",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Ekonomi", "Pendidikan", "Keuangan"],
|
||||||
|
"deskripsi": "Uang saku bulanan dan bantuan UKT (Uang Kuliah Tunggal).",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0",
|
||||||
|
"Lolos seleksi administrasi dan wawancara"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://indbeasiswa.com/beasiswa-bakti-bca/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nama": "Beasiswa BCA Finance",
|
||||||
|
"penyelenggara": "BCA Finance",
|
||||||
|
"jenjang": ["S1"],
|
||||||
|
"bidang_studi": ["Ekonomi", "Keuangan"],
|
||||||
|
"deskripsi": "Bantuan biaya kuliah per semester.",
|
||||||
|
"persyaratan": [
|
||||||
|
"WNI",
|
||||||
|
"Memiliki IPK minimal 3.0"
|
||||||
|
],
|
||||||
|
"deadline": "Biasanya dibuka setiap tahun",
|
||||||
|
"link": "https://indbeasiswa.com/beasiswa-bca-finance/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"data_ekonomi": [
|
||||||
|
{
|
||||||
|
"kode": "E01",
|
||||||
|
"deskripsi": "Saya dan keluarga memiliki kondisi ekonomi yang cukup untuk melanjutkan kuliah"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode": "E02",
|
||||||
|
"deskripsi": "Saya perlu mempertimbangkan biaya karena kondisi ekonomi keluarga terbatas"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode": "E03",
|
||||||
|
"deskripsi": "Saya berminat kuliah tapi berencana mencari beasiswa atau bantuan pendanaan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode": "E04",
|
||||||
|
"deskripsi": "Saya lebih memilih bekerja atau usaha karena kondisi ekonomi saat ini"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 6.8 KiB |
|
@ -0,0 +1,734 @@
|
||||||
|
{
|
||||||
|
"J01A-Kerja": {
|
||||||
|
"name": "IPA (Sains Murni) - Kerja",
|
||||||
|
"description": "Fokus: Biologi, Kimia, Fisika (pekerjaan di bidang kesehatan, sains, farmasi, laboratorium, lingkungan) tanpa kuliah formal, bisa belajar mandiri melalui kursus, sertifikasi, dan pelatihan.",
|
||||||
|
"categories": [
|
||||||
|
"Kesehatan & Sains",
|
||||||
|
"Penelitian",
|
||||||
|
"Pekerjaan Sains",
|
||||||
|
"Teknis Laboratorium"
|
||||||
|
],
|
||||||
|
"minat": {
|
||||||
|
"Kedokteran": {
|
||||||
|
"pertanyaan": [
|
||||||
|
"AKER01: Saya tertarik bekerja di rumah sakit atau klinik tanpa kuliah formal [6]",
|
||||||
|
"AKER02: Saya tertarik ingin mempelajari cara dasar merawat pasien atau membantu mereka sembuh [7]",
|
||||||
|
"AKER03: Saya tertarik untuk mendapatkan pengetahuan medis melalui kursus online atau pelatihan [6]",
|
||||||
|
"AKER04: Saya tertarik ingin bekerja sebagai asisten medis atau tenaga medis tanpa harus menjadi dokter [5]",
|
||||||
|
"AKER05: Saya tertarik dengan pekerjaan administrasi di lingkungan rumah sakit atau klinik [5]",
|
||||||
|
"AKER06: Saya tertarik memiliki empati tinggi dan ingin membantu pasien secara langsung [5]"
|
||||||
|
],
|
||||||
|
"karir": [
|
||||||
|
"AKAR01: Asisten Medis [20]",
|
||||||
|
"AKAR02: Tenaga Kesehatan [20]",
|
||||||
|
"AKAR03: Staf Klinik [20]",
|
||||||
|
"AKAR04: Administrasi Rumah Sakit [20]",
|
||||||
|
"AKAR05: Teknisi Alat Kesehatan [20]",
|
||||||
|
"AKAR06: Customer Service Asuransi Kesehatan [20]"
|
||||||
|
],
|
||||||
|
"jurusan_terkait": [
|
||||||
|
"D3 Keperawatan",
|
||||||
|
"D3 Teknologi Laboratorium Medik",
|
||||||
|
"D3 Farmasi",
|
||||||
|
"D3 Rekam Medis dan Informasi Kesehatan",
|
||||||
|
"D3 Teknologi Bank Darah",
|
||||||
|
"S1 Kesehatan Masyarakat"
|
||||||
|
],
|
||||||
|
"rekomendasi_kursus": [
|
||||||
|
"Pelatihan Bantuan Hidup Dasar (BHD)",
|
||||||
|
"Kursus Asisten Perawat Tersertifikasi",
|
||||||
|
"Pelatihan Administrasi Rumah Sakit",
|
||||||
|
"Sertifikasi Teknisi Alat Kesehatan"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Sains": {
|
||||||
|
"pertanyaan": [
|
||||||
|
"AKER07: Saya tertarik ingin menjadi peneliti atau bekerja di laboratorium [7]",
|
||||||
|
"AKER08: Saya tertarik senang melakukan eksperimen ilmiah dan mempelajari data percobaan [6]",
|
||||||
|
"AKER09: Saya tertarik untuk bekerja di bidang riset atau pengembangan produk tanpa kuliah formal [6]",
|
||||||
|
"AKER10: Saya tertarik ingin bekerja dengan tim sains di industri atau lembaga penelitian [6]",
|
||||||
|
"AKER11: Saya tertarik teliti dan mampu mengikuti protokol dengan tepat [5]",
|
||||||
|
"AKER12: Saya tertarik dalam pengolahan dan analisis data penelitian [5]"
|
||||||
|
],
|
||||||
|
"karir": [
|
||||||
|
"AKAR07: Teknisi Laboratorium [20]",
|
||||||
|
"AKAR08: Teknisi Peralatan Sains [20]",
|
||||||
|
"AKAR09: Staf R&D [20]",
|
||||||
|
"AKAR10: Pengumpul Data Lapangan [20]",
|
||||||
|
"AKAR11: Operator Peralatan Penelitian [20]",
|
||||||
|
"AKAR12: Staf Pengujian Kualitas [20]"
|
||||||
|
],
|
||||||
|
"jurusan_terkait": [
|
||||||
|
"D3 Analis Kimia",
|
||||||
|
"D3 Teknik Kimia",
|
||||||
|
"S1 Kimia",
|
||||||
|
"S1 Biologi",
|
||||||
|
"S1 Fisika",
|
||||||
|
"D3 MIPA"
|
||||||
|
],
|
||||||
|
"rekomendasi_kursus": [
|
||||||
|
"Sertifikasi Teknisi Laboratorium",
|
||||||
|
"Pelatihan Good Laboratory Practice (GLP)",
|
||||||
|
"Kursus Pengoperasian Alat Laboratorium",
|
||||||
|
"Pelatihan Analisis Data Dasar",
|
||||||
|
"Sertifikasi K3 Laboratorium"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Farmasi": {
|
||||||
|
"pertanyaan": [
|
||||||
|
"AKER13: Saya tertarik untuk bekerja di apotek atau di industri farmasi tanpa kuliah formal [6]",
|
||||||
|
"AKER14: Saya tertarik ingin mempelajari bagaimana obat bekerja melalui kursus atau pelatihan singkat [6]",
|
||||||
|
"AKER15: Saya tertarik dengan pengujian obat dan kualitas farmasi di industri [6]",
|
||||||
|
"AKER16: Saya tertarik ingin menjadi bagian dari tim yang mengembangkan produk obat [6]",
|
||||||
|
"AKER17: Saya tertarik teliti dalam mengukur dan mencatat informasi penting [6]",
|
||||||
|
"AKER18: Saya tertarik dalam penyimpanan dan pendistribusian obat-obatan [6]"
|
||||||
|
],
|
||||||
|
"riasecType": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R",
|
||||||
|
"C"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I",
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I",
|
||||||
|
"S"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"C"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"C",
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"karir": [
|
||||||
|
"AKAR13: Asisten Apoteker [20]",
|
||||||
|
"AKAR14: Teknisi Farmasi [20]",
|
||||||
|
"AKAR15: Staf Produksi Obat [20]",
|
||||||
|
"AKAR16: Pengujian Kualitas Obat [20]",
|
||||||
|
"AKAR17: Staf Logistik Farmasi [20]",
|
||||||
|
"AKAR18: Sales Representative Produk Farmasi [20]",
|
||||||
|
"AKAR19: Staf Administrasi Apotek [20]"
|
||||||
|
],
|
||||||
|
"karir_riasec": [
|
||||||
|
"I/C",
|
||||||
|
"I/R",
|
||||||
|
"R/C",
|
||||||
|
"I/C",
|
||||||
|
"C",
|
||||||
|
"E/S",
|
||||||
|
"C"
|
||||||
|
],
|
||||||
|
"jurusan_terkait": [
|
||||||
|
"D3 Farmasi",
|
||||||
|
"S1 Farmasi",
|
||||||
|
"D3 Teknik Kimia",
|
||||||
|
"D3 Analis Kimia",
|
||||||
|
"S1 Kimia Farmasi"
|
||||||
|
],
|
||||||
|
"rekomendasi_kursus": [
|
||||||
|
"Pelatihan Asisten Apoteker",
|
||||||
|
"Sertifikasi Good Manufacturing Practice (GMP)",
|
||||||
|
"Kursus Pengelolaan Obat",
|
||||||
|
"Pelatihan Teknik Penjualan Produk Farmasi",
|
||||||
|
"Kursus Komputer untuk Administrasi Apotek"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Laboratorium": {
|
||||||
|
"pertanyaan": [
|
||||||
|
"AKER19: Saya tertarik bekerja dengan peralatan dan instrumen laboratorium [6]",
|
||||||
|
"AKER20: Saya tertarik menyukai pekerjaan yang membutuhkan ketelitian dan konsistensi tinggi [6]",
|
||||||
|
"AKER21: Saya tertarik ingin bekerja di laboratorium klinik, industri, atau penelitian [6]",
|
||||||
|
"AKER22: Saya tertarik untuk mempelajari teknik-teknik analisis laboratorium [6]",
|
||||||
|
"AKER23: Saya tertarik mampu mengikuti protokol dan prosedur standar dengan tepat [6]",
|
||||||
|
"AKER24: Saya tertarik dalam persiapan sampel dan pengujian bahan [6]"
|
||||||
|
],
|
||||||
|
"riasecType": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R",
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"C"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
6
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"C"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I",
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"karir": [
|
||||||
|
"AKAR20: Teknisi Laboratorium Klinik [20]",
|
||||||
|
"AKAR21: Teknisi Laboratorium Industri [20]",
|
||||||
|
"AKAR22: Staf Pengujian Kualitas [20]",
|
||||||
|
"AKAR23: Teknisi Instrumentasi [20]",
|
||||||
|
"AKAR24: Staf Pengambilan Sampel [20]"
|
||||||
|
],
|
||||||
|
"karir_riasec": [
|
||||||
|
"I/R",
|
||||||
|
"I/R",
|
||||||
|
"I/C",
|
||||||
|
"R/I",
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"jurusan_terkait": [
|
||||||
|
"D3 Teknologi Laboratorium Medik",
|
||||||
|
"D3 Analis Kimia",
|
||||||
|
"D3 Analis Kesehatan",
|
||||||
|
"S1 Kimia",
|
||||||
|
"S1 Biologi",
|
||||||
|
"D3 Mikrobiologi"
|
||||||
|
],
|
||||||
|
"rekomendasi_kursus": [
|
||||||
|
"Sertifikasi Teknisi Laboratorium",
|
||||||
|
"Pelatihan Teknik Dasar Mikrobiologi",
|
||||||
|
"Kursus Instrumentasi Laboratorium",
|
||||||
|
"Pelatihan Sistem Manajemen Mutu Laboratorium",
|
||||||
|
"Kursus Pengambilan dan Penanganan Sampel"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Lingkungan": {
|
||||||
|
"pertanyaan": [
|
||||||
|
"AKER25: Saya tertarik untuk bekerja dalam pemantauan dan pengelolaan lingkungan [6]",
|
||||||
|
"AKER26: Saya tertarik peduli terhadap isu-isu lingkungan dan pelestarian alam [7]",
|
||||||
|
"AKER27: Saya tertarik suka bekerja di lapangan dan mengumpulkan data lingkungan [7]",
|
||||||
|
"AKER28: Saya tertarik untuk mempelajari cara mengukur polusi dan kualitas lingkungan [6]",
|
||||||
|
"AKER29: Saya tertarik ingin bekerja dalam proyek konservasi atau pengelolaan limbah [6]",
|
||||||
|
"AKER30: Saya tertarik dalam pendidikan lingkungan dan kampanye kesadaran [7]"
|
||||||
|
],
|
||||||
|
"riasecType": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I",
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"S",
|
||||||
|
"A"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R",
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R",
|
||||||
|
"S"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"S",
|
||||||
|
"A"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"karir": [
|
||||||
|
"AKAR25: Teknisi Pemantauan Lingkungan [20]",
|
||||||
|
"AKAR26: Staf Pengelolaan Limbah [20]",
|
||||||
|
"AKAR27: Asisten Pengambil Sampel Lingkungan [20]",
|
||||||
|
"AKAR28: Operator Pengolahan Air [20]",
|
||||||
|
"AKAR29: Staf Lapangan Konservasi [20]",
|
||||||
|
"AKAR30: Teknisi Pengendalian Polusi [20]"
|
||||||
|
],
|
||||||
|
"karir_riasec": [
|
||||||
|
"I/R",
|
||||||
|
"R/C",
|
||||||
|
"R/I",
|
||||||
|
"R",
|
||||||
|
"R/S",
|
||||||
|
"I/R"
|
||||||
|
],
|
||||||
|
"jurusan_terkait": [
|
||||||
|
"D3 Teknik Lingkungan",
|
||||||
|
"S1 Teknik Lingkungan",
|
||||||
|
"S1 Ilmu Lingkungan",
|
||||||
|
"S1 Biologi Lingkungan",
|
||||||
|
"D3 Pengelolaan Sumber Daya Air dan Lingkungan"
|
||||||
|
],
|
||||||
|
"rekomendasi_kursus": [
|
||||||
|
"Sertifikasi Teknisi Lingkungan",
|
||||||
|
"Pelatihan Pengelolaan Limbah",
|
||||||
|
"Kursus Pengambilan Sampel Lingkungan",
|
||||||
|
"Pelatihan Operator Instalasi Pengolahan Air",
|
||||||
|
"Kursus K3 Lingkungan"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Pangan dan Nutrisi": {
|
||||||
|
"pertanyaan": [
|
||||||
|
"AKER31: Saya tertarik bekerja dalam pengujian dan pengembangan produk pangan [6]",
|
||||||
|
"AKER32: Saya tertarik ingin mempelajari cara menganalisis kualitas dan keamanan makanan [7]",
|
||||||
|
"AKER33: Saya tertarik untuk bekerja di industri pengolahan pangan [5]",
|
||||||
|
"AKER34: Saya tertarik peduli tentang nutrisi dan dampaknya terhadap kesehatan [5]",
|
||||||
|
"AKER35: Saya tertarik dalam produksi dan pengawasan makanan [5]",
|
||||||
|
"AKER36: Saya tertarik ingin membantu orang dengan masalah nutrisi dan diet [4]"
|
||||||
|
],
|
||||||
|
"riasecType": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I",
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"I",
|
||||||
|
"S"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R",
|
||||||
|
"C"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"S"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
4
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"karir": [
|
||||||
|
"AKAR31: Teknisi Pengujian Pangan [20]",
|
||||||
|
"AKAR32: Asisten Quality Control Makanan [20]",
|
||||||
|
"AKAR33: Staf Produksi Industri Pangan [20]",
|
||||||
|
"AKAR34: Teknisi Pengolahan Makanan [20]",
|
||||||
|
"AKAR35: Staf Pengembangan Produk Pangan [20]",
|
||||||
|
"AKAR36: Asisten Food Safety [20]"
|
||||||
|
],
|
||||||
|
"karir_riasec": [
|
||||||
|
"I/R",
|
||||||
|
"C/I",
|
||||||
|
"R",
|
||||||
|
"R/I",
|
||||||
|
"I/R",
|
||||||
|
"C/I"
|
||||||
|
],
|
||||||
|
"jurusan_terkait": [
|
||||||
|
"D3 Teknologi Pangan",
|
||||||
|
"S1 Ilmu dan Teknologi Pangan",
|
||||||
|
"D3 Gizi",
|
||||||
|
"S1 Ilmu Gizi",
|
||||||
|
"D3 Pengawasan Mutu Pangan"
|
||||||
|
],
|
||||||
|
"rekomendasi_kursus": [
|
||||||
|
"Sertifikasi Keamanan Pangan (HACCP)",
|
||||||
|
"Pelatihan Quality Control Makanan",
|
||||||
|
"Kursus Pengujian Nutrisi Pangan",
|
||||||
|
"Pelatihan Good Manufacturing Practice (GMP) untuk Pangan",
|
||||||
|
"Kursus Dasar Nutrisi dan Diet"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Peralatan Medis": {
|
||||||
|
"pertanyaan": [
|
||||||
|
"AKER37: Saya tertarik bekerja dengan peralatan dan teknologi medis [6]",
|
||||||
|
"AKER38: Saya tertarik ingin mempelajari cara mengoperasikan dan memelihara alat medis [7]",
|
||||||
|
"AKER39: Saya tertarik untuk bekerja di rumah sakit atau perusahaan peralatan medis [5]",
|
||||||
|
"AKER40: Saya tertarik memiliki keterampilan teknis dan ingin menerapkannya di bidang kesehatan [6]",
|
||||||
|
"AKER41: Saya tertarik dalam pengenalan dan pelatihan penggunaan peralatan medis [5]",
|
||||||
|
"AKER42: Saya tertarik suka memperbaiki dan memecahkan masalah pada perangkat elektronik [6]"
|
||||||
|
],
|
||||||
|
"riasecType": [
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R",
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"S",
|
||||||
|
"E"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R",
|
||||||
|
"I"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
4,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"S",
|
||||||
|
"E"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
3,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": [
|
||||||
|
"R"
|
||||||
|
],
|
||||||
|
"bobot": [
|
||||||
|
5
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"karir": [
|
||||||
|
"AKAR37: Teknisi Peralatan Medis [20]",
|
||||||
|
"AKAR38: Teknisi Laboratorium Medis [20]",
|
||||||
|
"AKAR39: Staf Penjualan Alat Kesehatan [20]",
|
||||||
|
"AKAR40: Teknisi Pemeliharaan Alat Medis [20]",
|
||||||
|
"AKAR41: Staf Logistik Peralatan Medis [20]"
|
||||||
|
],
|
||||||
|
"karir_riasec": [
|
||||||
|
"R/I",
|
||||||
|
"I/R",
|
||||||
|
"E/S",
|
||||||
|
"R",
|
||||||
|
"C/R"
|
||||||
|
],
|
||||||
|
"jurusan_terkait": [
|
||||||
|
"D3 Teknik Elektromedik",
|
||||||
|
"D3 Teknik Elektronika",
|
||||||
|
"D3 Teknik Biomedis",
|
||||||
|
"S1 Teknik Biomedis",
|
||||||
|
"D3 Teknik Instrumentasi"
|
||||||
|
],
|
||||||
|
"rekomendasi_kursus": [
|
||||||
|
"Sertifikasi Teknisi Peralatan Medis",
|
||||||
|
"Pelatihan Dasar Radiologi",
|
||||||
|
"Kursus Elektrokardiografi",
|
||||||
|
"Pelatihan Pemeliharaan Peralatan Laboratorium",
|
||||||
|
"Kursus Penjualan Alat Kesehatan"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"semua_pertanyaan": [
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER01",
|
||||||
|
"isi_pertanyaan": "Saya tertarik bekerja di rumah sakit atau klinik tanpa kuliah formal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER02",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin mempelajari cara dasar merawat pasien atau membantu mereka sembuh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER03",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk mendapatkan pengetahuan medis melalui kursus online atau pelatihan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER04",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin bekerja sebagai asisten medis atau tenaga medis tanpa harus menjadi dokter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER05",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dengan pekerjaan administrasi di lingkungan rumah sakit atau klinik"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER06",
|
||||||
|
"isi_pertanyaan": "Saya tertarik memiliki empati tinggi dan ingin membantu pasien secara langsung"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER07",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin menjadi peneliti atau bekerja di laboratorium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER08",
|
||||||
|
"isi_pertanyaan": "Saya tertarik senang melakukan eksperimen ilmiah dan mempelajari data percobaan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER09",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk bekerja di bidang riset atau pengembangan produk tanpa kuliah formal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER10",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin bekerja dengan tim sains di industri atau lembaga penelitian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER11",
|
||||||
|
"isi_pertanyaan": "Saya tertarik teliti dan mampu mengikuti protokol dengan tepat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER12",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dalam pengolahan dan analisis data penelitian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER13",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk bekerja di apotek atau di industri farmasi tanpa kuliah formal"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER14",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin mempelajari bagaimana obat bekerja melalui kursus atau pelatihan singkat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER15",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dengan pengujian obat dan kualitas farmasi di industri"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER16",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin menjadi bagian dari tim yang mengembangkan produk obat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER17",
|
||||||
|
"isi_pertanyaan": "Saya tertarik teliti dalam mengukur dan mencatat informasi penting"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER18",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dalam penyimpanan dan pendistribusian obat-obatan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER19",
|
||||||
|
"isi_pertanyaan": "Saya tertarik bekerja dengan peralatan dan instrumen laboratorium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER20",
|
||||||
|
"isi_pertanyaan": "Saya tertarik menyukai pekerjaan yang membutuhkan ketelitian dan konsistensi tinggi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER21",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin bekerja di laboratorium klinik, industri, atau penelitian"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER22",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk mempelajari teknik-teknik analisis laboratorium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER23",
|
||||||
|
"isi_pertanyaan": "Saya tertarik mampu mengikuti protokol dan prosedur standar dengan tepat"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER24",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dalam persiapan sampel dan pengujian bahan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER25",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk bekerja dalam pemantauan dan pengelolaan lingkungan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER26",
|
||||||
|
"isi_pertanyaan": "Saya tertarik peduli terhadap isu-isu lingkungan dan pelestarian alam"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER27",
|
||||||
|
"isi_pertanyaan": "Saya tertarik suka bekerja di lapangan dan mengumpulkan data lingkungan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER28",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk mempelajari cara mengukur polusi dan kualitas lingkungan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER29",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin bekerja dalam proyek konservasi atau pengelolaan limbah"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER30",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dalam pendidikan lingkungan dan kampanye kesadaran"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER31",
|
||||||
|
"isi_pertanyaan": "Saya tertarik bekerja dalam pengujian dan pengembangan produk pangan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER32",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin mempelajari cara menganalisis kualitas dan keamanan makanan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER33",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk bekerja di industri pengolahan pangan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER34",
|
||||||
|
"isi_pertanyaan": "Saya tertarik peduli tentang nutrisi dan dampaknya terhadap kesehatan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER35",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dalam produksi dan pengawasan makanan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER36",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin membantu orang dengan masalah nutrisi dan diet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER37",
|
||||||
|
"isi_pertanyaan": "Saya tertarik bekerja dengan peralatan dan teknologi medis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER38",
|
||||||
|
"isi_pertanyaan": "Saya tertarik ingin mempelajari cara mengoperasikan dan memelihara alat medis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER39",
|
||||||
|
"isi_pertanyaan": "Saya tertarik untuk bekerja di rumah sakit atau perusahaan peralatan medis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER40",
|
||||||
|
"isi_pertanyaan": "Saya tertarik memiliki keterampilan teknis dan ingin menerapkannya di bidang kesehatan"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER41",
|
||||||
|
"isi_pertanyaan": "Saya tertarik dalam pengenalan dan pelatihan penggunaan peralatan medis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"kode_pertanyaan": "AKER42",
|
||||||
|
"isi_pertanyaan": "Saya tertarik suka memperbaiki dan memecahkan masalah pada perangkat elektronik"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 6.1 MiB |
|
@ -0,0 +1,3 @@
|
||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
|
@ -0,0 +1 @@
|
||||||
|
{"flutter":{"platforms":{"android":{"default":{"projectId":"sirekomendasi-dc7de","appId":"1:1072369607324:android:f76a929d6f6258726f4359","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"sirekomendasi-dc7de","configurations":{"android":"1:1072369607324:android:f76a929d6f6258726f4359","ios":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","macos":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","web":"1:1072369607324:web:d17906f3452e68cb6f4359","windows":"1:1072369607324:web:7c9de102a40074a66f4359"}}},"ios":{"default":{"projectId":"sirekomendasi-dc7de","appId":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"macos":{"default":{"projectId":"sirekomendasi-dc7de","appId":"1:1072369607324:ios:1d8cb1dbadb289ff6f4359","uploadDebugSymbols":false,"fileOutput":"macos/Runner/GoogleService-Info.plist"}}}}}
|
|
@ -0,0 +1,34 @@
|
||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
platform :ios, '13.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
pod 'Firebase/Analytics', :modular_headers => true
|
||||||
|
pod 'Firebase/Auth', :modular_headers => true
|
||||||
|
pod 'Firebase/Core', :modular_headers => true
|
||||||
|
pod 'Firebase/Firestore', :modular_headers => true
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
if target.name == 'BoringSSL-GRPC'
|
||||||
|
target.source_build_phase.files.each do |file|
|
||||||
|
if file.settings && file.settings['COMPILER_FLAGS']
|
||||||
|
flags = file.settings['COMPILER_FLAGS'].split
|
||||||
|
flags.reject! { |flag| flag == '-GCC_WARN_INHIBIT_ALL_WARNINGS' }
|
||||||
|
file.settings['COMPILER_FLAGS'] = flags.join(' ')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,753 @@
|
||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
0B08F4E3191C1718CE721DEE /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44309CC712138A6C295BDD13 /* Pods_Runner.framework */; };
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
41BE7AE37D217557707F77F4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A61884E6070EB92C9DA7565 /* Pods_RunnerTests.framework */; };
|
||||||
|
619C01BB6DEEA093B9F64580 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 71E56F6CBCC9735A933C8324 /* GoogleService-Info.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
090C37EAACC6628C1878F26D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
3001B99FAF58D3847964C671 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3A61884E6070EB92C9DA7565 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
40E1C4F703AEAFB3F4E96C40 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
44309CC712138A6C295BDD13 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
6623077AAAEF559F09EAAEC6 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
71E56F6CBCC9735A933C8324 /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
A8A46054CD2BDDAEAD8EBD4C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
E4D15238BF2B8882453091C1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
822140EDA9A65D30FEC797F5 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
41BE7AE37D217557707F77F4 /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
0B08F4E3191C1718CE721DEE /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
767DA86CBE420F5563DA86EC /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
A8A46054CD2BDDAEAD8EBD4C /* Pods-Runner.debug.xcconfig */,
|
||||||
|
3001B99FAF58D3847964C671 /* Pods-Runner.release.xcconfig */,
|
||||||
|
40E1C4F703AEAFB3F4E96C40 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
E4D15238BF2B8882453091C1 /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
|
6623077AAAEF559F09EAAEC6 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
|
090C37EAACC6628C1878F26D /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
767DA86CBE420F5563DA86EC /* Pods */,
|
||||||
|
EBB65BF4410896E755CE9345 /* Frameworks */,
|
||||||
|
71E56F6CBCC9735A933C8324 /* GoogleService-Info.plist */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EBB65BF4410896E755CE9345 /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
44309CC712138A6C295BDD13 /* Pods_Runner.framework */,
|
||||||
|
3A61884E6070EB92C9DA7565 /* Pods_RunnerTests.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
EE15A8A241E794CA6B46D3BF /* [CP] Check Pods Manifest.lock */,
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
822140EDA9A65D30FEC797F5 /* Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
FB0BF3469EFA785B86C304D7 /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
978F70D62289D467CBC98B93 /* [CP] Embed Pods Frameworks */,
|
||||||
|
09CC5E7467F1BA4502F52B26 /* [CP] Copy Pods Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
619C01BB6DEEA093B9F64580 /* GoogleService-Info.plist in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
09CC5E7467F1BA4502F52B26 /* [CP] Copy Pods Resources */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Copy Pods Resources";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
978F70D62289D467CBC98B93 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
EE15A8A241E794CA6B46D3BF /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
FB0BF3469EFA785B86C304D7 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = L6P4GD56Q6;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = E4D15238BF2B8882453091C1 /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 6623077AAAEF559F09EAAEC6 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 090C37EAACC6628C1878F26D /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = L6P4GD56Q6;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = L6P4GD56Q6;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.forwardChainingManApp;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 295 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 450 B |
After Width: | Height: | Size: 282 B |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 406 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 862 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 762 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 68 B |
After Width: | Height: | Size: 68 B |
After Width: | Height: | Size: 68 B |
|
@ -0,0 +1,5 @@
|
||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CLIENT_ID</key>
|
||||||
|
<string>1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5.apps.googleusercontent.com</string>
|
||||||
|
<key>REVERSED_CLIENT_ID</key>
|
||||||
|
<string>com.googleusercontent.apps.1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5</string>
|
||||||
|
<key>ANDROID_CLIENT_ID</key>
|
||||||
|
<string>1072369607324-hp2vujt95ipkft4n348a96ndbiocqrdo.apps.googleusercontent.com</string>
|
||||||
|
<key>API_KEY</key>
|
||||||
|
<string>AIzaSyDI7KTuPhqGiIkX5pznUDYkToUmIhznqf8</string>
|
||||||
|
<key>GCM_SENDER_ID</key>
|
||||||
|
<string>1072369607324</string>
|
||||||
|
<key>PLIST_VERSION</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>BUNDLE_ID</key>
|
||||||
|
<string>com.example.forwardChainingManApp</string>
|
||||||
|
<key>PROJECT_ID</key>
|
||||||
|
<string>sirekomendasi-dc7de</string>
|
||||||
|
<key>STORAGE_BUCKET</key>
|
||||||
|
<string>sirekomendasi-dc7de.firebasestorage.app</string>
|
||||||
|
<key>IS_ADS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_ANALYTICS_ENABLED</key>
|
||||||
|
<false></false>
|
||||||
|
<key>IS_APPINVITE_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_GCM_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>IS_SIGNIN_ENABLED</key>
|
||||||
|
<true></true>
|
||||||
|
<key>GOOGLE_APP_ID</key>
|
||||||
|
<string>1:1072369607324:ios:1d8cb1dbadb289ff6f4359</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
|
<string>We need permission to save photos and videos to your library for your convenience.</string>
|
||||||
|
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>We need permission to access your photo library to view and select your photos and videos.</string>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Forward Chaining Man App</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>forward_chaining_man_app</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleURLTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Editor</string>
|
||||||
|
<key>CFBundleURLSchemes</key>
|
||||||
|
<array>
|
||||||
|
<!-- TODO Replace this value: -->
|
||||||
|
<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
|
||||||
|
<string>com.googleusercontent.apps.1072369607324-0e0k7pn3nqk6suq97jnegljp3k0161o5</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1 @@
|
||||||
|
#import "GeneratedPluginRegistrant.h"
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' as rootBundle;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
/// Bagian Model & Fungsi Pendukung
|
||||||
|
////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Representasi data ProgramStudi (bawaan dari JSON)
|
||||||
|
class ProgramStudi {
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
final List<String> categories;
|
||||||
|
final Map<String, Minat> minat;
|
||||||
|
|
||||||
|
ProgramStudi({
|
||||||
|
required this.name,
|
||||||
|
required this.description,
|
||||||
|
required this.categories,
|
||||||
|
required this.minat,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ProgramStudi.fromJson(Map<String, dynamic> json) {
|
||||||
|
final minatMap = <String, Minat>{};
|
||||||
|
if (json['minat'] != null) {
|
||||||
|
(json['minat'] as Map<String, dynamic>).forEach((key, value) {
|
||||||
|
minatMap[key] = Minat.fromJson(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProgramStudi(
|
||||||
|
name: json['name'] ?? '',
|
||||||
|
description: json['description'] ?? '',
|
||||||
|
categories: json['categories'] == null
|
||||||
|
? []
|
||||||
|
: List<String>.from(json['categories']),
|
||||||
|
minat: minatMap,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Representasi data Minat (pertanyaan, karir, dsb.)
|
||||||
|
class Minat {
|
||||||
|
final List<String> pertanyaan;
|
||||||
|
final List<String> karir;
|
||||||
|
final List<String> jurusanTerkait;
|
||||||
|
|
||||||
|
Minat({
|
||||||
|
required this.pertanyaan,
|
||||||
|
required this.karir,
|
||||||
|
required this.jurusanTerkait,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory Minat.fromJson(Map<String, dynamic> json) {
|
||||||
|
return Minat(
|
||||||
|
pertanyaan: json['pertanyaan'] == null
|
||||||
|
? []
|
||||||
|
: List<String>.from(json['pertanyaan']),
|
||||||
|
karir: json['karir'] == null ? [] : List<String>.from(json['karir']),
|
||||||
|
jurusanTerkait: json['jurusan_terkait'] == null
|
||||||
|
? []
|
||||||
|
: List<String>.from(json['jurusan_terkait']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Model flattened satu pertanyaan
|
||||||
|
class QuestionItem {
|
||||||
|
final String id; // unique ID, misal "Q1", "Q2"
|
||||||
|
final String programName; // ex: "IPA (Sains Murni) - Kerja"
|
||||||
|
final String minatKey; // ex: "Kedokteran"
|
||||||
|
final String questionText; // Cleaned question text
|
||||||
|
final String rawQuestionText; // Original question text with code
|
||||||
|
final int bobot; // ex: 5
|
||||||
|
bool? userAnswer; // Jawaban user: true, false, atau null
|
||||||
|
|
||||||
|
// Add this field to store the question code
|
||||||
|
final String questionCode; // ex: "KUL04"
|
||||||
|
|
||||||
|
// Add any other existing fields...
|
||||||
|
|
||||||
|
QuestionItem({
|
||||||
|
required this.id,
|
||||||
|
required this.programName,
|
||||||
|
required this.minatKey,
|
||||||
|
required this.questionText,
|
||||||
|
required this.rawQuestionText,
|
||||||
|
required this.bobot,
|
||||||
|
this.userAnswer,
|
||||||
|
required this.questionCode, // Add to constructor
|
||||||
|
// Add any other existing parameters...
|
||||||
|
});
|
||||||
|
|
||||||
|
// If needed, you can keep a factory method to extract code from raw text
|
||||||
|
factory QuestionItem.fromRawQuestion({
|
||||||
|
required String id,
|
||||||
|
required String programName,
|
||||||
|
required String minatKey,
|
||||||
|
required String questionText,
|
||||||
|
required String rawQuestionText,
|
||||||
|
required int bobot,
|
||||||
|
bool? userAnswer,
|
||||||
|
// Any other existing parameters...
|
||||||
|
}) {
|
||||||
|
// Extract question code
|
||||||
|
final regex = RegExp(r'([A-Z]+\d+):');
|
||||||
|
final match = regex.firstMatch(rawQuestionText);
|
||||||
|
final questionCode = match != null && match.groupCount >= 1
|
||||||
|
? match.group(1)!
|
||||||
|
: id; // Fallback to ID if no code found
|
||||||
|
|
||||||
|
return QuestionItem(
|
||||||
|
id: id,
|
||||||
|
programName: programName,
|
||||||
|
minatKey: minatKey,
|
||||||
|
questionText: questionText,
|
||||||
|
rawQuestionText: rawQuestionText,
|
||||||
|
bobot: bobot,
|
||||||
|
userAnswer: userAnswer,
|
||||||
|
questionCode: questionCode,
|
||||||
|
// Pass any other existing parameters...
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fungsi untuk mengambil bobot [n] dari string pertanyaan
|
||||||
|
int extractBobot(String pertanyaan) {
|
||||||
|
final regex = RegExp(r"\[(\d+)\]");
|
||||||
|
final match = regex.firstMatch(pertanyaan);
|
||||||
|
if (match != null) {
|
||||||
|
return int.parse(match.group(1)!);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fungsi untuk menghapus teks [n] di akhir pertanyaan
|
||||||
|
String cleanPertanyaan(String pertanyaan) {
|
||||||
|
return pertanyaan.replaceAll(RegExp(r"\[\d+\]"), "").trim();
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
bool developerMode = false;
|
||||||
|
|
||||||
|
/// Controller untuk DeveloperModePage
|
||||||
|
class DeveloperModeController extends GetxController {
|
||||||
|
final RxBool isDeveloperMode = developerMode.obs;
|
||||||
|
|
||||||
|
void toggleDeveloperMode(bool value) {
|
||||||
|
isDeveloperMode.value = value;
|
||||||
|
developerMode = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
/// Controller untuk HomePage
|
||||||
|
class HomeController extends GetxController {
|
||||||
|
final Rx<bool?> pilihan =
|
||||||
|
Rx<bool?>(null); // null=belum pilih; true=Kerja; false=Kuliah
|
||||||
|
|
||||||
|
void setPilihan(bool? val) {
|
||||||
|
pilihan.value = val;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,976 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' as rootBundle;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/about/widget/diagram_painter.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_profile.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class AboutPage extends StatefulWidget {
|
||||||
|
const AboutPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AboutPage> createState() => _AboutPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForwardChainingLogoPainter extends CustomPainter {
|
||||||
|
final double animationValue;
|
||||||
|
|
||||||
|
ForwardChainingLogoPainter({required this.animationValue});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final centerX = size.width / 2;
|
||||||
|
final centerY = size.height / 2;
|
||||||
|
final radius = size.width * 0.32;
|
||||||
|
|
||||||
|
// Create a layered neural network structure with input, hidden, and output nodes
|
||||||
|
|
||||||
|
// Define the layers (3 layers: input, hidden, output)
|
||||||
|
final int inputNodes = 4;
|
||||||
|
final int hiddenNodes = 6;
|
||||||
|
final int outputNodes = 3;
|
||||||
|
|
||||||
|
// Node positions for each layer
|
||||||
|
final List<Offset> inputLayer = [];
|
||||||
|
final List<Offset> hiddenLayer = [];
|
||||||
|
final List<Offset> outputLayer = [];
|
||||||
|
|
||||||
|
// Create a gradient for the background glow
|
||||||
|
final Rect rect =
|
||||||
|
Rect.fromCircle(center: Offset(centerX, centerY), radius: radius * 1.2);
|
||||||
|
final gradient = RadialGradient(
|
||||||
|
colors: [
|
||||||
|
Colors.indigo.shade400.withOpacity(0.2),
|
||||||
|
Colors.transparent,
|
||||||
|
],
|
||||||
|
stops: const [0.5, 1.0],
|
||||||
|
);
|
||||||
|
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..shader = gradient.createShader(rect)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
canvas.drawCircle(Offset(centerX, centerY), radius * 1.2, backgroundPaint);
|
||||||
|
|
||||||
|
// Paints for nodes
|
||||||
|
final inputNodePaint = Paint()
|
||||||
|
..color = Colors.blue.shade400
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
final hiddenNodePaint = Paint()
|
||||||
|
..color = Colors.indigo.shade400
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
final outputNodePaint = Paint()
|
||||||
|
..color = Colors.purple.shade400
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
// Paint for node glows
|
||||||
|
final glowPaint = Paint()
|
||||||
|
..color = Colors.indigo.shade200.withOpacity(0.4)
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 8);
|
||||||
|
|
||||||
|
// Paint for node borders
|
||||||
|
final borderPaint = Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.8)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.5;
|
||||||
|
|
||||||
|
// Paint for connections with animation
|
||||||
|
final connectionPaint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.5;
|
||||||
|
|
||||||
|
// Create input layer nodes (left side)
|
||||||
|
final inputSpacing = size.height / (inputNodes + 1);
|
||||||
|
for (int i = 0; i < inputNodes; i++) {
|
||||||
|
final y = (i + 1) * inputSpacing;
|
||||||
|
final pulseFactor = math
|
||||||
|
.sin((animationValue * 2 * math.pi) + (i * 0.5))
|
||||||
|
.clamp(-0.5, 0.5) *
|
||||||
|
0.05;
|
||||||
|
final offsetX = (math.sin(animationValue * math.pi + i) * 4).clamp(-4, 4);
|
||||||
|
|
||||||
|
// Position slightly to the left of center
|
||||||
|
inputLayer.add(Offset(
|
||||||
|
centerX - (radius * 0.7) + offsetX, y + (pulseFactor * size.height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create hidden layer nodes (center)
|
||||||
|
final hiddenSpacing = size.height / (hiddenNodes + 1);
|
||||||
|
for (int i = 0; i < hiddenNodes; i++) {
|
||||||
|
final y = (i + 1) * hiddenSpacing;
|
||||||
|
final pulseFactor = math
|
||||||
|
.sin((animationValue * 2 * math.pi) + (i * 0.7))
|
||||||
|
.clamp(-0.5, 0.5) *
|
||||||
|
0.03;
|
||||||
|
final offsetY =
|
||||||
|
(math.sin(animationValue * math.pi * 2 + i * 0.5) * 5).clamp(-5, 5);
|
||||||
|
|
||||||
|
// Position at center
|
||||||
|
hiddenLayer
|
||||||
|
.add(Offset(centerX + (pulseFactor * size.width), y + offsetY));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create output layer nodes (right side)
|
||||||
|
final outputSpacing = size.height / (outputNodes + 1);
|
||||||
|
for (int i = 0; i < outputNodes; i++) {
|
||||||
|
final y = (i + 1) * outputSpacing;
|
||||||
|
final pulseFactor = math
|
||||||
|
.sin((animationValue * 2 * math.pi) + (i * 0.9))
|
||||||
|
.clamp(-0.5, 0.5) *
|
||||||
|
0.05;
|
||||||
|
final offsetX =
|
||||||
|
(math.sin(animationValue * math.pi + i * 1.2) * 4).clamp(-4, 4);
|
||||||
|
|
||||||
|
// Position to the right of center
|
||||||
|
outputLayer.add(Offset(
|
||||||
|
centerX + (radius * 0.7) + offsetX, y + (pulseFactor * size.height)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw connections with animated data flow
|
||||||
|
void drawConnections(
|
||||||
|
List<Offset> fromLayer, List<Offset> toLayer, Color baseColor) {
|
||||||
|
for (int i = 0; i < fromLayer.length; i++) {
|
||||||
|
for (int j = 0; j < toLayer.length; j++) {
|
||||||
|
// Create a flow effect along the connection
|
||||||
|
final path = Path();
|
||||||
|
path.moveTo(fromLayer[i].dx, fromLayer[i].dy);
|
||||||
|
path.lineTo(toLayer[j].dx, toLayer[j].dy);
|
||||||
|
|
||||||
|
// Create gradient shader for data flow effect
|
||||||
|
final pathMetrics = path.computeMetrics().first;
|
||||||
|
final length = pathMetrics.length;
|
||||||
|
|
||||||
|
// Animate a dot along the path
|
||||||
|
final flowPosition =
|
||||||
|
(animationValue * 2 + (i * 0.1) + (j * 0.05)) % 1.0;
|
||||||
|
final flowPoint =
|
||||||
|
pathMetrics.getTangentForOffset(length * flowPosition)?.position;
|
||||||
|
|
||||||
|
// Basic line
|
||||||
|
connectionPaint.color = baseColor.withOpacity(0.3 +
|
||||||
|
(0.2 * math.sin(animationValue * math.pi * 2 + i + j))
|
||||||
|
.clamp(0.0, 0.5));
|
||||||
|
canvas.drawPath(path, connectionPaint);
|
||||||
|
|
||||||
|
// Draw data flow point
|
||||||
|
if (flowPoint != null && (i + j) % 2 == 0) {
|
||||||
|
// Only draw on some connections to avoid clutter
|
||||||
|
final flowDotPaint = Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.7)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
canvas.drawCircle(flowPoint, 1.5, flowDotPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw connections from input to hidden layer
|
||||||
|
drawConnections(inputLayer, hiddenLayer, Colors.blue.shade500);
|
||||||
|
|
||||||
|
// Draw connections from hidden to output layer
|
||||||
|
drawConnections(hiddenLayer, outputLayer, Colors.purple.shade500);
|
||||||
|
|
||||||
|
// Draw the nodes with glow effect
|
||||||
|
void drawNodesWithEffects(
|
||||||
|
List<Offset> nodes, Paint nodePaint, double size) {
|
||||||
|
for (int i = 0; i < nodes.length; i++) {
|
||||||
|
final node = nodes[i];
|
||||||
|
// Size pulsation
|
||||||
|
final pulse =
|
||||||
|
1.0 + 0.15 * math.sin((animationValue * 2 * math.pi) + (i));
|
||||||
|
final nodeSize = size * pulse.clamp(0.9, 1.15);
|
||||||
|
|
||||||
|
// Draw glow
|
||||||
|
canvas.drawCircle(node, nodeSize * 1.5, glowPaint);
|
||||||
|
|
||||||
|
// Draw node
|
||||||
|
canvas.drawCircle(node, nodeSize, nodePaint);
|
||||||
|
|
||||||
|
// Draw border
|
||||||
|
canvas.drawCircle(node, nodeSize, borderPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw all nodes by layer
|
||||||
|
drawNodesWithEffects(inputLayer, inputNodePaint, 5);
|
||||||
|
drawNodesWithEffects(hiddenLayer, hiddenNodePaint, 6);
|
||||||
|
drawNodesWithEffects(outputLayer, outputNodePaint, 5);
|
||||||
|
|
||||||
|
// Draw central circle highlight
|
||||||
|
final centerGlowPaint = Paint()
|
||||||
|
..color = Colors.indigo.withOpacity(
|
||||||
|
(0.1 + 0.05 * math.sin(animationValue * math.pi * 2))
|
||||||
|
.clamp(0.05, 0.15))
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 15);
|
||||||
|
|
||||||
|
canvas.drawCircle(Offset(centerX, centerY), radius * 0.4, centerGlowPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(ForwardChainingLogoPainter oldDelegate) {
|
||||||
|
return oldDelegate.animationValue != animationValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AboutPageState extends State<AboutPage> with TickerProviderStateMixin {
|
||||||
|
late AnimationController _logoAnimationController;
|
||||||
|
late AnimationController _cardAnimationController;
|
||||||
|
late Animation<double> _logoRotationAnimation;
|
||||||
|
late Animation<double> _logoScaleAnimation;
|
||||||
|
late Animation<double> _fadeInAnimation;
|
||||||
|
late List<Animation<Offset>> _cardSlideAnimations;
|
||||||
|
|
||||||
|
// For step animation
|
||||||
|
int _currentStep = 0;
|
||||||
|
final int _totalSteps = 4;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// Logo animation controller
|
||||||
|
_logoAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
)..repeat(reverse: true);
|
||||||
|
|
||||||
|
// Card animation controller
|
||||||
|
_cardAnimationController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 1200),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Logo animations
|
||||||
|
// Fix: Ensure the end value is <= 1.0 to prevent the assertion error
|
||||||
|
_logoRotationAnimation = Tween<double>(begin: 0, end: 0.05).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _logoAnimationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fix: Ensure the end value doesn't cause any curves to go beyond 1.0
|
||||||
|
_logoScaleAnimation = Tween<double>(begin: 1.0, end: 1.08).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _logoAnimationController,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_fadeInAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _cardAnimationController,
|
||||||
|
// Fix: Ensure the end of the interval is <= 1.0
|
||||||
|
curve: const Interval(0, 0.6, curve: Curves.easeOut),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Card slide animations (staggered)
|
||||||
|
_cardSlideAnimations = [
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
Tween<Offset>(
|
||||||
|
begin: const Offset(0, 0.5),
|
||||||
|
end: Offset.zero,
|
||||||
|
).animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _cardAnimationController,
|
||||||
|
curve: Interval(
|
||||||
|
0.2 + (i * 0.12),
|
||||||
|
(0.7 + (i * 0.08)).clamp(0.0, 1.0), // ✅ Perbaikan utama!
|
||||||
|
curve: Curves.easeOutCubic,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Start animations
|
||||||
|
_cardAnimationController.forward();
|
||||||
|
|
||||||
|
// Start step animation
|
||||||
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
|
_startStepAnimation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _startStepAnimation() {
|
||||||
|
Future.delayed(const Duration(seconds: 3), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_currentStep = (_currentStep + 1) % _totalSteps;
|
||||||
|
});
|
||||||
|
_startStepAnimation();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_logoAnimationController.dispose();
|
||||||
|
_cardAnimationController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [
|
||||||
|
Colors.indigo.shade900,
|
||||||
|
Colors.blue.shade800,
|
||||||
|
Colors.blue.shade700,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// App Bar
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_back,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const Text(
|
||||||
|
'Tentang Aplikasi',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
const SizedBox(width: 40), // Balance the layout
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Animated Logo
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Title and Tagline
|
||||||
|
FadeTransition(
|
||||||
|
opacity: _fadeInAnimation,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'EduGuide',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Sistem Rekomendasi Karir & Jurusan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
// About App Card
|
||||||
|
SlideTransition(
|
||||||
|
position: _cardSlideAnimations[0],
|
||||||
|
child: _buildInfoCard(
|
||||||
|
title: 'Tentang Aplikasi',
|
||||||
|
icon: Icons.info_outline,
|
||||||
|
color: Colors.blue.shade300,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Aplikasi EduGuide adalah sistem pakar berbasis aturan (rule-based expert system) yang menggunakan metode inferensi forward chaining untuk memberikan rekomendasi jurusan dan karir yang sesuai dengan minat pengguna.',
|
||||||
|
style: TextStyle(fontSize: 14, height: 1.5),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Aplikasi ini dikembangkan sebagai bagian dari tugas akhir/skripsi untuk menunjukkan implementasi praktis dari metode forward chaining dalam sistem pendukung keputusan.',
|
||||||
|
style: TextStyle(fontSize: 14, height: 1.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// How It Works Card
|
||||||
|
SlideTransition(
|
||||||
|
position: _cardSlideAnimations[1],
|
||||||
|
child: _buildInfoCard(
|
||||||
|
title: 'Cara Kerja Forward Chaining',
|
||||||
|
icon: Icons.lightbulb_outline,
|
||||||
|
color: Colors.orange.shade300,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Forward Chaining adalah metode penalaran dari fakta-fakta yang diketahui menuju kesimpulan. Dalam aplikasi ini:',
|
||||||
|
style: TextStyle(fontSize: 14, height: 1.5),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Animated steps
|
||||||
|
_buildAnimatedStepExplanation(),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.amber.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.amber.shade200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.tips_and_updates,
|
||||||
|
color: Colors.amber.shade700,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Dengan metode ini, sistem dapat memberikan rekomendasi yang paling sesuai berdasarkan minat kamu!',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Forward Chaining Visual Explanation
|
||||||
|
SlideTransition(
|
||||||
|
position: _cardSlideAnimations[2],
|
||||||
|
child: _buildInfoCard(
|
||||||
|
title: 'Visualisasi Proses',
|
||||||
|
icon: Icons.bar_chart,
|
||||||
|
color: Colors.green.shade300,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Image.asset(
|
||||||
|
'assets/forward_chaining_diagram.png',
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
height: 180,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
// Fallback if image not available
|
||||||
|
return _buildForwardChainingDiagram();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const Text(
|
||||||
|
'Forward Chaining bekerja dengan mengevaluasi jawaban kamu dan mencocokkannya dengan aturan (rules) untuk menemukan rekomendasi terbaik. Ini seperti menyelesaikan teka-teki dengan petunjuk yang kamu berikan.',
|
||||||
|
style: TextStyle(fontSize: 14, height: 1.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Tech Stack Card
|
||||||
|
SlideTransition(
|
||||||
|
position: _cardSlideAnimations[3],
|
||||||
|
child: _buildInfoCard(
|
||||||
|
title: 'Teknologi',
|
||||||
|
icon: Icons.code,
|
||||||
|
color: Colors.purple.shade300,
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children: [
|
||||||
|
_buildTechChip(
|
||||||
|
label: 'Flutter', icon: Icons.flutter_dash),
|
||||||
|
_buildTechChip(
|
||||||
|
label: 'Dart', icon: Icons.extension),
|
||||||
|
_buildTechChip(
|
||||||
|
label: 'Forward Chaining',
|
||||||
|
icon: Icons.account_tree),
|
||||||
|
_buildTechChip(
|
||||||
|
label: 'GetX', icon: Icons.auto_awesome),
|
||||||
|
_buildTechChip(
|
||||||
|
label: 'Rule-Based System', icon: Icons.rule),
|
||||||
|
_buildTechChip(
|
||||||
|
label: 'Expert System',
|
||||||
|
icon: Icons.psychology),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Developer Card
|
||||||
|
SlideTransition(
|
||||||
|
position: _cardSlideAnimations[4],
|
||||||
|
child: _buildInfoCard(
|
||||||
|
title: 'Pengembang',
|
||||||
|
icon: Icons.person,
|
||||||
|
color: Colors.amber.shade300,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
image: const DecorationImage(
|
||||||
|
image: AssetImage('assets/profile_dev.png'),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
// Use a placeholder if no image is available
|
||||||
|
onError: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Yanuar Tri Laksono',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
const Text(
|
||||||
|
'Mahasiswa Informatika',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
SocialButton(
|
||||||
|
icon: FontAwesomeIcons
|
||||||
|
.envelope, // Email icon
|
||||||
|
onTap: () {
|
||||||
|
launchUrl(Uri.parse(
|
||||||
|
'mailto:yanuartrilaksono23@gmail.com'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
SocialButton(
|
||||||
|
icon: FontAwesomeIcons
|
||||||
|
.linkedin, // Portfolio link icon
|
||||||
|
onTap: () {
|
||||||
|
launchUrl(Uri.parse(
|
||||||
|
'https://www.linkedin.com/in/yanuar-tri-laksono/'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
SocialButton(
|
||||||
|
icon: FontAwesomeIcons
|
||||||
|
.github, // GitHub icon
|
||||||
|
onTap: () {
|
||||||
|
launchUrl(Uri.parse(
|
||||||
|
'https://github.com/Greek-Cp'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Footer
|
||||||
|
FadeTransition(
|
||||||
|
opacity: _fadeInAnimation,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'© ${DateTime.now().year} EduGuide App',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withOpacity(0.8),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Versi 1.0.0',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withOpacity(0.6),
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the animated step explanation
|
||||||
|
Widget _buildAnimatedStepExplanation() {
|
||||||
|
final List<Map<String, dynamic>> steps = [
|
||||||
|
{
|
||||||
|
'icon': Icons.question_answer,
|
||||||
|
'title': 'Langkah 1: Mengumpulkan Fakta',
|
||||||
|
'content':
|
||||||
|
'Sistem mengumpulkan jawaban "Ya" atau "Tidak" dari semua pertanyaanmu dan menyimpannya sebagai fakta.',
|
||||||
|
'color': Colors.blue.shade700,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.rule,
|
||||||
|
'title': 'Langkah 2: Mencocokkan Aturan',
|
||||||
|
'content':
|
||||||
|
'Sistem mencocokkan jawabanmu dengan aturan-aturan minat dan karir. Setiap jawaban "Ya" akan menambah skor pada minat tertentu.',
|
||||||
|
'color': Colors.green.shade700,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.calculate,
|
||||||
|
'title': 'Langkah 3: Menghitung Skor',
|
||||||
|
'content':
|
||||||
|
'Skor untuk setiap minat dan karir dihitung berdasarkan pertanyaan yang kamu jawab "Ya".',
|
||||||
|
'color': Colors.orange.shade700,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.star,
|
||||||
|
'title': 'Langkah 4: Memberikan Rekomendasi',
|
||||||
|
'content':
|
||||||
|
'Sistem mengurutkan hasil dan menampilkan 3 minat dengan skor tertinggi sebagai rekomendasi terbaikmu.',
|
||||||
|
'color': Colors.purple.shade700,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: steps.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final step = entry.value;
|
||||||
|
final isActive = index == _currentStep;
|
||||||
|
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
isActive ? step['color'].withOpacity(0.1) : Colors.grey.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: isActive ? step['color'] : Colors.grey.shade200,
|
||||||
|
width: isActive ? 2 : 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 500),
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isActive ? step['color'] : Colors.grey.shade200,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
step['icon'],
|
||||||
|
color: isActive ? Colors.white : Colors.grey.shade600,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
step['title'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 14,
|
||||||
|
color: isActive ? step['color'] : Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
step['content'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
height: 1.4,
|
||||||
|
color: isActive ? Colors.black87 : Colors.grey.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback Forward Chaining diagram if image is not available
|
||||||
|
Widget _buildForwardChainingDiagram() {
|
||||||
|
return Container(
|
||||||
|
height: 180,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: ForwardChainingDiagramPainter(),
|
||||||
|
size: const Size(double.infinity, 180),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Animated logo content with nodes and connections
|
||||||
|
Widget _buildAnimatedLogoContent() {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: ForwardChainingLogoPainter(
|
||||||
|
animationValue: _logoAnimationController.value,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.psychology,
|
||||||
|
size: 70,
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Card widget with consistent styling
|
||||||
|
Widget _buildInfoCard({
|
||||||
|
required String title,
|
||||||
|
required IconData icon,
|
||||||
|
required Color color,
|
||||||
|
required Widget child,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Card Header
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withOpacity(0.2),
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(24),
|
||||||
|
topRight: Radius.circular(24),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Card Content
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tech stack chip
|
||||||
|
Widget _buildTechChip({required String label, required IconData icon}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 16,
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Social media/contact button
|
||||||
|
Widget _buildSocialButton({
|
||||||
|
required IconData icon,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SocialButton extends StatelessWidget {
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
const SocialButton({
|
||||||
|
required this.icon,
|
||||||
|
required this.onTap,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: FaIcon(
|
||||||
|
icon,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' as rootBundle;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_profile.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class ForwardChainingDiagramPainter extends CustomPainter {
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final width = size.width;
|
||||||
|
final height = size.height;
|
||||||
|
|
||||||
|
// Define colors
|
||||||
|
final workingMemoryColor = Colors.blue.shade100;
|
||||||
|
final rulesColor = Colors.orange.shade100;
|
||||||
|
final resultsColor = Colors.green.shade100;
|
||||||
|
final arrowColor = Colors.grey.shade700;
|
||||||
|
|
||||||
|
// Define paint objects
|
||||||
|
final boxPaint = Paint()..style = PaintingStyle.fill;
|
||||||
|
final borderPaint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..color = Colors.grey.shade600
|
||||||
|
..strokeWidth = 1.5;
|
||||||
|
|
||||||
|
final textStyle = TextStyle(
|
||||||
|
color: Colors.black87,
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
);
|
||||||
|
|
||||||
|
final titleStyle = TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Helper function to draw boxes with text
|
||||||
|
void drawBox(String title, String content, Rect rect, Color color) {
|
||||||
|
// Draw box
|
||||||
|
boxPaint.color = color;
|
||||||
|
canvas.drawRRect(
|
||||||
|
RRect.fromRectAndRadius(rect, Radius.circular(8)),
|
||||||
|
boxPaint,
|
||||||
|
);
|
||||||
|
canvas.drawRRect(
|
||||||
|
RRect.fromRectAndRadius(rect, Radius.circular(8)),
|
||||||
|
borderPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw title
|
||||||
|
final titleSpan = TextSpan(text: title, style: titleStyle);
|
||||||
|
final titlePainter = TextPainter(
|
||||||
|
text: titleSpan,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
titlePainter.layout(maxWidth: rect.width - 10);
|
||||||
|
titlePainter.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(rect.left + 5, rect.top + 5),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw content
|
||||||
|
final contentSpan = TextSpan(text: content, style: textStyle);
|
||||||
|
final contentPainter = TextPainter(
|
||||||
|
text: contentSpan,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
contentPainter.layout(maxWidth: rect.width - 10);
|
||||||
|
contentPainter.paint(
|
||||||
|
canvas,
|
||||||
|
Offset(rect.left + 5, rect.top + 25),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to draw arrows
|
||||||
|
void drawArrow(Offset start, Offset end) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = arrowColor
|
||||||
|
..strokeWidth = 1.5
|
||||||
|
..style = PaintingStyle.stroke;
|
||||||
|
|
||||||
|
// Draw line
|
||||||
|
canvas.drawLine(start, end, paint);
|
||||||
|
|
||||||
|
// Draw arrowhead
|
||||||
|
final delta = end - start;
|
||||||
|
final angle = delta.direction;
|
||||||
|
final arrowSize = 8.0;
|
||||||
|
|
||||||
|
final arrowPath = Path()
|
||||||
|
..moveTo(end.dx, end.dy)
|
||||||
|
..lineTo(
|
||||||
|
end.dx - arrowSize * math.cos(angle - math.pi / 6),
|
||||||
|
end.dy - arrowSize * math.sin(angle - math.pi / 6),
|
||||||
|
)
|
||||||
|
..lineTo(
|
||||||
|
end.dx - arrowSize * math.cos(angle + math.pi / 6),
|
||||||
|
end.dy - arrowSize * math.sin(angle + math.pi / 6),
|
||||||
|
)
|
||||||
|
..close();
|
||||||
|
|
||||||
|
canvas.drawPath(arrowPath, Paint()..color = arrowColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate box positions
|
||||||
|
final workingMemoryRect = Rect.fromLTWH(20, 20, width * 0.25, height - 40);
|
||||||
|
final rulesRect =
|
||||||
|
Rect.fromLTWH(width * 0.33, 20, width * 0.25, height - 40);
|
||||||
|
final resultsRect =
|
||||||
|
Rect.fromLTWH(width * 0.66, 20, width * 0.25, height - 40);
|
||||||
|
|
||||||
|
// Draw the boxes
|
||||||
|
drawBox(
|
||||||
|
'Working Memory',
|
||||||
|
'Facts:\n• Q1=Yes\n• Q2=No\n• Q3=Yes\n...',
|
||||||
|
workingMemoryRect,
|
||||||
|
workingMemoryColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
drawBox(
|
||||||
|
'Rules',
|
||||||
|
'IF Q1=Yes THEN Skor+3\nIF Q2=Yes THEN Skor+2\nIF Q3=Yes AND Q4=Yes\n THEN Skor+4\n...',
|
||||||
|
rulesRect,
|
||||||
|
rulesColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
drawBox(
|
||||||
|
'Results (Top 3)',
|
||||||
|
'1. IPA | Kedokteran (24)\n2. IPS | Ekonomi (19)\n3. IPA | Teknik (16)',
|
||||||
|
resultsRect,
|
||||||
|
resultsColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw arrows
|
||||||
|
drawArrow(
|
||||||
|
Offset(workingMemoryRect.right, workingMemoryRect.center.dy - 20),
|
||||||
|
Offset(rulesRect.left, rulesRect.center.dy - 20),
|
||||||
|
);
|
||||||
|
|
||||||
|
drawArrow(
|
||||||
|
Offset(rulesRect.right, rulesRect.center.dy),
|
||||||
|
Offset(resultsRect.left, resultsRect.center.dy),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw loop arrow for rule evaluation
|
||||||
|
final loopStart = Offset(rulesRect.right - 20, rulesRect.bottom - 25);
|
||||||
|
final loopControl1 = Offset(rulesRect.right + 20, rulesRect.bottom + 15);
|
||||||
|
final loopControl2 = Offset(rulesRect.left - 20, rulesRect.bottom + 15);
|
||||||
|
final loopEnd = Offset(rulesRect.left, rulesRect.bottom - 25);
|
||||||
|
|
||||||
|
final loopPath = Path()
|
||||||
|
..moveTo(loopStart.dx, loopStart.dy)
|
||||||
|
..cubicTo(
|
||||||
|
loopControl1.dx,
|
||||||
|
loopControl1.dy,
|
||||||
|
loopControl2.dx,
|
||||||
|
loopControl2.dy,
|
||||||
|
loopEnd.dx,
|
||||||
|
loopEnd.dy,
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.drawPath(
|
||||||
|
loopPath,
|
||||||
|
Paint()
|
||||||
|
..color = arrowColor
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.2,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw arrowhead for loop
|
||||||
|
final loopDelta = Offset(0, -5);
|
||||||
|
final loopAngle = loopDelta.direction;
|
||||||
|
final arrowSize = 7.0;
|
||||||
|
|
||||||
|
final loopArrowPath = Path()
|
||||||
|
..moveTo(loopEnd.dx, loopEnd.dy)
|
||||||
|
..lineTo(
|
||||||
|
loopEnd.dx - arrowSize * math.cos(loopAngle - math.pi / 6),
|
||||||
|
loopEnd.dy - arrowSize * math.sin(loopAngle - math.pi / 6),
|
||||||
|
)
|
||||||
|
..lineTo(
|
||||||
|
loopEnd.dx - arrowSize * math.cos(loopAngle + math.pi / 6),
|
||||||
|
loopEnd.dy - arrowSize * math.sin(loopAngle + math.pi / 6),
|
||||||
|
)
|
||||||
|
..close();
|
||||||
|
|
||||||
|
canvas.drawPath(loopArrowPath, Paint()..color = arrowColor);
|
||||||
|
|
||||||
|
// Add "Rule Evaluation Loop" text
|
||||||
|
final loopTextSpan = TextSpan(
|
||||||
|
text: "Rule Evaluation Loop",
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.grey.shade800,
|
||||||
|
fontSize: 9,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final loopTextPainter = TextPainter(
|
||||||
|
text: loopTextSpan,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
);
|
||||||
|
loopTextPainter.layout();
|
||||||
|
loopTextPainter.paint(
|
||||||
|
canvas, Offset(rulesRect.center.dx - 35, rulesRect.bottom + 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
|
@ -0,0 +1,817 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class ProfileController extends GetxController {
|
||||||
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
var isLoading = false.obs;
|
||||||
|
var isEditing = false.obs;
|
||||||
|
var userProfile = Rx<Map<String, dynamic>>({});
|
||||||
|
var schoolId = ''.obs;
|
||||||
|
|
||||||
|
final nameController = TextEditingController();
|
||||||
|
final selectedClass = Rx<String?>(null);
|
||||||
|
|
||||||
|
// Class options for the dropdown
|
||||||
|
final List<String> classOptions = [
|
||||||
|
'X IPA A (1)',
|
||||||
|
'X IPA B (2)',
|
||||||
|
'X IPA C (3)',
|
||||||
|
'X IPA D (4)',
|
||||||
|
'XI IPA A (1)',
|
||||||
|
'XI IPA B (2)',
|
||||||
|
'XI IPA C (3)',
|
||||||
|
'XI IPA D (4)',
|
||||||
|
'XII IPA A (1)',
|
||||||
|
'XII IPA B (2)',
|
||||||
|
'XII IPA C (3)',
|
||||||
|
'XII IPA D (4)',
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
loadUserProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
nameController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> loadUserProfile() async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
User? currentUser = _auth.currentUser;
|
||||||
|
if (currentUser == null) {
|
||||||
|
Get.offAll(() => StudentLoginPage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get schoolId from SharedPreferences
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final storedSchoolId = prefs.getString('school_id');
|
||||||
|
|
||||||
|
if (storedSchoolId != null && storedSchoolId.isNotEmpty) {
|
||||||
|
schoolId.value = storedSchoolId;
|
||||||
|
|
||||||
|
// Get student data from the subcollection
|
||||||
|
final docSnapshot = await _firestore
|
||||||
|
.collection('schools')
|
||||||
|
.doc(schoolId.value)
|
||||||
|
.collection('students')
|
||||||
|
.doc(currentUser.uid)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (docSnapshot.exists) {
|
||||||
|
userProfile.value = docSnapshot.data() as Map<String, dynamic>;
|
||||||
|
|
||||||
|
// Initialize controllers with current values
|
||||||
|
nameController.text = userProfile.value['name'] ?? '';
|
||||||
|
selectedClass.value = userProfile.value['class'];
|
||||||
|
} else {
|
||||||
|
// Student not found in this school
|
||||||
|
await _findStudentInAllSchools(currentUser);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// School ID not found in shared preferences, search in all schools
|
||||||
|
await _findStudentInAllSchools(currentUser);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal memuat profil: ${e.toString()}',
|
||||||
|
backgroundColor: Colors.red.shade100,
|
||||||
|
colorText: Colors.red.shade800,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to find a student in all schools
|
||||||
|
Future<void> _findStudentInAllSchools(User currentUser) async {
|
||||||
|
try {
|
||||||
|
final schoolsSnapshot = await _firestore.collection('schools').get();
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
for (var schoolDoc in schoolsSnapshot.docs) {
|
||||||
|
final studentDoc = await schoolDoc.reference
|
||||||
|
.collection('students')
|
||||||
|
.doc(currentUser.uid)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (studentDoc.exists) {
|
||||||
|
// Found the student
|
||||||
|
schoolId.value = schoolDoc.id;
|
||||||
|
userProfile.value = studentDoc.data() as Map<String, dynamic>;
|
||||||
|
|
||||||
|
// Save school ID to SharedPreferences
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('school_id', schoolId.value);
|
||||||
|
|
||||||
|
// Initialize controllers with current values
|
||||||
|
nameController.text = userProfile.value['name'] ?? '';
|
||||||
|
selectedClass.value = userProfile.value['class'];
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If student was not found in any school
|
||||||
|
if (!found) {
|
||||||
|
nameController.text = currentUser.displayName ?? '';
|
||||||
|
selectedClass.value = null;
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Perhatian',
|
||||||
|
'Profil tidak ditemukan. Silakan lengkapi data profil Anda.',
|
||||||
|
backgroundColor: Colors.orange.shade100,
|
||||||
|
colorText: Colors.orange.shade800,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enter edit mode automatically
|
||||||
|
isEditing.value = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error finding student in schools: $e');
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void toggleEditMode() {
|
||||||
|
isEditing.value = !isEditing.value;
|
||||||
|
|
||||||
|
// Reset controllers to current values when canceling edit
|
||||||
|
if (!isEditing.value) {
|
||||||
|
nameController.text = userProfile.value['name'] ?? '';
|
||||||
|
selectedClass.value = userProfile.value['class'];
|
||||||
|
} else {
|
||||||
|
selectedClass.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveProfile() async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
if (nameController.text.trim().isEmpty ||
|
||||||
|
selectedClass.value == null ||
|
||||||
|
selectedClass.value!.isEmpty) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Nama dan kelas tidak boleh kosong',
|
||||||
|
backgroundColor: Colors.red.shade100,
|
||||||
|
colorText: Colors.red.shade800,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
User? currentUser = _auth.currentUser;
|
||||||
|
if (currentUser == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If schoolId is empty, we need to select a school
|
||||||
|
if (schoolId.value.isEmpty) {
|
||||||
|
// For simplicity, using the first school available
|
||||||
|
// In a real app, you might want to show a school selection UI
|
||||||
|
final schoolsSnapshot = await _firestore.collection('schools').get();
|
||||||
|
if (schoolsSnapshot.docs.isNotEmpty) {
|
||||||
|
schoolId.value = schoolsSnapshot.docs.first.id;
|
||||||
|
|
||||||
|
// Save school ID to SharedPreferences
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('school_id', schoolId.value);
|
||||||
|
} else {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Tidak ada sekolah yang tersedia',
|
||||||
|
backgroundColor: Colors.red.shade100,
|
||||||
|
colorText: Colors.red.shade800,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Firestore in the appropriate subcollection
|
||||||
|
await _firestore
|
||||||
|
.collection('schools')
|
||||||
|
.doc(schoolId.value)
|
||||||
|
.collection('students')
|
||||||
|
.doc(currentUser.uid)
|
||||||
|
.set({
|
||||||
|
'name': nameController.text.trim(),
|
||||||
|
'class': selectedClass.value,
|
||||||
|
'email': currentUser.email,
|
||||||
|
'photoURL': currentUser.photoURL,
|
||||||
|
'updatedAt': FieldValue.serverTimestamp(),
|
||||||
|
}, SetOptions(merge: true));
|
||||||
|
|
||||||
|
// Update display name if needed
|
||||||
|
if (currentUser.displayName != nameController.text.trim()) {
|
||||||
|
await currentUser.updateDisplayName(nameController.text.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload profile
|
||||||
|
await loadUserProfile();
|
||||||
|
|
||||||
|
// Exit edit mode
|
||||||
|
isEditing.value = false;
|
||||||
|
|
||||||
|
Get.snackbar(
|
||||||
|
'Sukses',
|
||||||
|
'Profil berhasil diperbarui',
|
||||||
|
backgroundColor: Colors.green.shade100,
|
||||||
|
colorText: Colors.green.shade800,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal menyimpan profil: ${e.toString()}',
|
||||||
|
backgroundColor: Colors.red.shade100,
|
||||||
|
colorText: Colors.red.shade800,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> logout() async {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
// Clear shared preferences
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.clear();
|
||||||
|
|
||||||
|
// Sign out from Firebase
|
||||||
|
await _auth.signOut();
|
||||||
|
|
||||||
|
// Navigate to login page
|
||||||
|
Get.offAll(() => IntroPage());
|
||||||
|
} catch (e) {
|
||||||
|
Get.snackbar(
|
||||||
|
'Error',
|
||||||
|
'Gagal keluar: ${e.toString()}',
|
||||||
|
backgroundColor: Colors.red.shade100,
|
||||||
|
colorText: Colors.red.shade800,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProfilePage extends StatelessWidget {
|
||||||
|
const ProfilePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final controller = Get.put(ProfileController());
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text(
|
||||||
|
'Profil Saya',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.indigo.shade800,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
elevation: 0,
|
||||||
|
actions: [
|
||||||
|
Obx(
|
||||||
|
() => controller.isEditing.value
|
||||||
|
? IconButton(
|
||||||
|
onPressed: controller.toggleEditMode,
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
tooltip: 'Batal',
|
||||||
|
)
|
||||||
|
: IconButton(
|
||||||
|
onPressed: controller.toggleEditMode,
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
tooltip: 'Edit Profil',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.indigo.shade800,
|
||||||
|
Colors.blue.shade800,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
bottom: false,
|
||||||
|
child: Obx(
|
||||||
|
() => controller.isLoading.value
|
||||||
|
? const Center(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Profile Header
|
||||||
|
_buildProfileHeader(controller),
|
||||||
|
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Profile Content
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Informasi Profil',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.indigo.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Profile Fields
|
||||||
|
Obx(
|
||||||
|
() => controller.isEditing.value
|
||||||
|
? _buildEditFields(controller)
|
||||||
|
: _buildDisplayFields(controller),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Save Button (only in edit mode)
|
||||||
|
Obx(
|
||||||
|
() => controller.isEditing.value
|
||||||
|
? ElevatedButton(
|
||||||
|
onPressed: controller.saveProfile,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor:
|
||||||
|
Colors.indigo.shade800,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(
|
||||||
|
vertical: 16),
|
||||||
|
minimumSize: const Size(
|
||||||
|
double.infinity, 56),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.save, size: 20),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
'Simpan Perubahan',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Logout Button
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.dialog(
|
||||||
|
AlertDialog(
|
||||||
|
title: const Text('Konfirmasi'),
|
||||||
|
content: const Text(
|
||||||
|
'Apakah Anda yakin ingin keluar?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Get.back(),
|
||||||
|
child: const Text('Batal'),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back();
|
||||||
|
controller.logout();
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
child: const Text('Keluar'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.red.shade50,
|
||||||
|
foregroundColor: Colors.red.shade700,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 16),
|
||||||
|
minimumSize:
|
||||||
|
const Size(double.infinity, 56),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.logout, size: 20),
|
||||||
|
SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
'Keluar Akun',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// App version
|
||||||
|
Center(
|
||||||
|
child: Text(
|
||||||
|
'App Version 1.0.0',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.grey.shade500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildProfileHeader(ProfileController controller) {
|
||||||
|
final User? currentUser = FirebaseAuth.instance.currentUser;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// Profile Picture
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 50,
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
backgroundImage: currentUser?.photoURL != null
|
||||||
|
? NetworkImage(currentUser!.photoURL!)
|
||||||
|
: null,
|
||||||
|
child: currentUser?.photoURL == null
|
||||||
|
? Text(
|
||||||
|
controller.userProfile.value['name'] != null
|
||||||
|
? controller.userProfile.value['name']
|
||||||
|
.substring(0, 1)
|
||||||
|
.toUpperCase()
|
||||||
|
: '?',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 40,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.indigo,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Name
|
||||||
|
Text(
|
||||||
|
controller.userProfile.value['name'] ?? 'Siswa',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
|
||||||
|
// Email
|
||||||
|
Text(
|
||||||
|
currentUser?.email ?? '',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Class Badge
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.school,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
controller.userProfile.value['class'] ?? 'Kelas tidak diatur',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDisplayFields(ProfileController controller) {
|
||||||
|
final User? currentUser = FirebaseAuth.instance.currentUser;
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
// Name field
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.person,
|
||||||
|
title: 'Nama Lengkap',
|
||||||
|
value: controller.userProfile.value['name'] ?? '-',
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Class field
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.school,
|
||||||
|
title: 'Kelas',
|
||||||
|
value: controller.userProfile.value['class'] ?? '-',
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Email field
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.email,
|
||||||
|
title: 'Email',
|
||||||
|
value: currentUser?.email ?? '-',
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Registration Type
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.login,
|
||||||
|
title: 'Jenis Registrasi',
|
||||||
|
value: controller.userProfile.value['registrationType'] == 'google'
|
||||||
|
? 'Google'
|
||||||
|
: 'Email & Password',
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Join Date
|
||||||
|
_buildInfoItem(
|
||||||
|
icon: Icons.calendar_today,
|
||||||
|
title: 'Tanggal Bergabung',
|
||||||
|
value: controller.userProfile.value['createdAt'] != null
|
||||||
|
? _formatTimestamp(controller.userProfile.value['createdAt'])
|
||||||
|
: '-',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEditFields(ProfileController controller) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Name field
|
||||||
|
const Text(
|
||||||
|
'Nama Lengkap',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
TextField(
|
||||||
|
controller: controller.nameController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
prefixIcon: Icon(Icons.person, color: Colors.indigo.shade300),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
borderSide: BorderSide(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
borderSide: BorderSide(color: Colors.indigo.shade500),
|
||||||
|
),
|
||||||
|
filled: true,
|
||||||
|
fillColor: Colors.grey.shade50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Class dropdown
|
||||||
|
const Text(
|
||||||
|
'Kelas',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
border: Border.all(color: Colors.grey.shade300),
|
||||||
|
),
|
||||||
|
child: Obx(() {
|
||||||
|
// Validate that value exists in items list
|
||||||
|
final currentValue = controller.selectedClass.value;
|
||||||
|
final isValueValid = currentValue != null &&
|
||||||
|
controller.classOptions.contains(currentValue);
|
||||||
|
|
||||||
|
return DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton<String>(
|
||||||
|
// Only set value if it's valid
|
||||||
|
value: isValueValid ? currentValue : null,
|
||||||
|
hint: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.school_outlined,
|
||||||
|
color: Colors.indigo.shade300),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Text('Pilih Kelas'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isExpanded: true,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
icon: Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 16),
|
||||||
|
child: Icon(Icons.arrow_drop_down,
|
||||||
|
color: Colors.indigo.shade400),
|
||||||
|
),
|
||||||
|
items: controller.classOptions.map((String kelas) {
|
||||||
|
return DropdownMenuItem<String>(
|
||||||
|
value: kelas,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.school_outlined,
|
||||||
|
color: Colors.indigo.shade300),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(kelas),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (String? newValue) {
|
||||||
|
controller.selectedClass.value = newValue;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoItem({
|
||||||
|
required IconData icon,
|
||||||
|
required String title,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
border: Border.all(color: Colors.grey.shade200),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.indigo.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: Colors.indigo.shade400,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatTimestamp(dynamic timestamp) {
|
||||||
|
if (timestamp == null) return '-';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (timestamp is Timestamp) {
|
||||||
|
final DateTime dateTime = timestamp.toDate();
|
||||||
|
return '${dateTime.day}/${dateTime.month}/${dateTime.year}';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error formatting timestamp: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,465 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
|
||||||
|
class TeacherRegisterController extends GetxController {
|
||||||
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
var isLoading = false.obs;
|
||||||
|
var errorMessage = ''.obs;
|
||||||
|
|
||||||
|
// Text controllers for registration form
|
||||||
|
final nameController = TextEditingController();
|
||||||
|
final emailController = TextEditingController();
|
||||||
|
final passwordController = TextEditingController();
|
||||||
|
final confirmPasswordController = TextEditingController();
|
||||||
|
final schoolController = TextEditingController();
|
||||||
|
final subjectController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onClose() {
|
||||||
|
nameController.dispose();
|
||||||
|
emailController.dispose();
|
||||||
|
passwordController.dispose();
|
||||||
|
confirmPasswordController.dispose();
|
||||||
|
schoolController.dispose();
|
||||||
|
subjectController.dispose();
|
||||||
|
super.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register new teacher account
|
||||||
|
Future<void> registerTeacher() async {
|
||||||
|
try {
|
||||||
|
// Reset error message
|
||||||
|
errorMessage.value = '';
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (nameController.text.trim().isEmpty ||
|
||||||
|
emailController.text.trim().isEmpty ||
|
||||||
|
passwordController.text.isEmpty ||
|
||||||
|
confirmPasswordController.text.isEmpty ||
|
||||||
|
schoolController.text.trim().isEmpty) {
|
||||||
|
errorMessage.value = 'Semua kolom wajib diisi';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password validation
|
||||||
|
if (passwordController.text.length < 6) {
|
||||||
|
errorMessage.value = 'Password minimal 6 karakter';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm password validation
|
||||||
|
if (passwordController.text != confirmPasswordController.text) {
|
||||||
|
errorMessage.value = 'Password tidak sama';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start loading
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
// Create user in Firebase Auth
|
||||||
|
final userCredential = await _auth.createUserWithEmailAndPassword(
|
||||||
|
email: emailController.text.trim(),
|
||||||
|
password: passwordController.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (userCredential.user != null) {
|
||||||
|
// Add teacher data to Firestore
|
||||||
|
await _firestore
|
||||||
|
.collection('teachers')
|
||||||
|
.doc(userCredential.user!.uid)
|
||||||
|
.set({
|
||||||
|
'name': nameController.text.trim(),
|
||||||
|
'email': emailController.text.trim(),
|
||||||
|
'school': schoolController.text.trim(),
|
||||||
|
'subject': subjectController.text.trim(),
|
||||||
|
'createdAt': FieldValue.serverTimestamp(),
|
||||||
|
'lastLogin': FieldValue.serverTimestamp(),
|
||||||
|
'role': 'teacher',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show success message
|
||||||
|
Get.snackbar(
|
||||||
|
'Berhasil',
|
||||||
|
'Akun guru berhasil dibuat',
|
||||||
|
backgroundColor: Colors.green.shade100,
|
||||||
|
colorText: Colors.green.shade800,
|
||||||
|
snackPosition: SnackPosition.BOTTOM,
|
||||||
|
margin: const EdgeInsets.all(16),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Navigate back to login
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
} on FirebaseAuthException catch (e) {
|
||||||
|
switch (e.code) {
|
||||||
|
case 'email-already-in-use':
|
||||||
|
errorMessage.value = 'Email sudah digunakan';
|
||||||
|
break;
|
||||||
|
case 'invalid-email':
|
||||||
|
errorMessage.value = 'Format email tidak valid';
|
||||||
|
break;
|
||||||
|
case 'weak-password':
|
||||||
|
errorMessage.value = 'Password terlalu lemah';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
errorMessage.value = 'Gagal mendaftar: ${e.message}';
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
errorMessage.value = 'Terjadi kesalahan: ${e.toString()}';
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TeacherRegisterPage extends StatelessWidget {
|
||||||
|
const TeacherRegisterPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final controller = Get.put(TeacherRegisterController());
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [
|
||||||
|
Colors.blue.shade800,
|
||||||
|
Colors.indigo.shade900,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Back button at the top
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8, bottom: 20),
|
||||||
|
child: TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8, horizontal: 16),
|
||||||
|
backgroundColor: Colors.white.withOpacity(0.2),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: const [
|
||||||
|
Icon(Icons.arrow_back, size: 18),
|
||||||
|
SizedBox(width: 8),
|
||||||
|
Text('Kembali ke Halaman Login'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// App Logo with Hero animation
|
||||||
|
Hero(
|
||||||
|
tag: 'app_logo',
|
||||||
|
child: Container(
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.2),
|
||||||
|
blurRadius: 15,
|
||||||
|
offset: const Offset(0, 8),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.psychology,
|
||||||
|
size: 60,
|
||||||
|
color: Colors.blue.shade700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// App Title
|
||||||
|
const Text(
|
||||||
|
'EduGuide',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
letterSpacing: 1.2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
|
// Register Subtitle
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Daftar Akun Guru',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Registration Card
|
||||||
|
Obx(() => Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.1),
|
||||||
|
blurRadius: 20,
|
||||||
|
offset: const Offset(0, 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.app_registration_rounded,
|
||||||
|
color: Colors.blue.shade600,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'Daftar Akun Baru',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
const Text(
|
||||||
|
'Lengkapi data diri Anda',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.black54,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Name field
|
||||||
|
CustomTextField(
|
||||||
|
controller: controller.nameController,
|
||||||
|
label: 'Nama Lengkap',
|
||||||
|
prefixIcon: Icons.person_outline,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Email field
|
||||||
|
CustomTextField(
|
||||||
|
controller: controller.emailController,
|
||||||
|
label: 'Email',
|
||||||
|
prefixIcon: Icons.email_outlined,
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// School field
|
||||||
|
CustomTextField(
|
||||||
|
controller: controller.schoolController,
|
||||||
|
label: 'Sekolah',
|
||||||
|
prefixIcon: Icons.school_outlined,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Subject field
|
||||||
|
CustomTextField(
|
||||||
|
controller: controller.subjectController,
|
||||||
|
label: 'Mata Pelajaran (Opsional)',
|
||||||
|
prefixIcon: Icons.book_outlined,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Password field
|
||||||
|
CustomTextField(
|
||||||
|
controller: controller.passwordController,
|
||||||
|
label: 'Password',
|
||||||
|
prefixIcon: Icons.lock_outline,
|
||||||
|
isPassword: true,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Confirm Password field
|
||||||
|
CustomTextField(
|
||||||
|
controller:
|
||||||
|
controller.confirmPasswordController,
|
||||||
|
label: 'Konfirmasi Password',
|
||||||
|
prefixIcon: Icons.lock_outline,
|
||||||
|
isPassword: true,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Obx(() => controller
|
||||||
|
.errorMessage.value.isNotEmpty
|
||||||
|
? Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red.shade50,
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.red.shade200,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.error_outline,
|
||||||
|
color: Colors.red.shade800),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
controller.errorMessage.value,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.red.shade700,
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink()),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Register button
|
||||||
|
_buildPrimaryButton(
|
||||||
|
label: 'Daftar',
|
||||||
|
icon: Icons.how_to_reg,
|
||||||
|
isLoading: controller.isLoading.value,
|
||||||
|
onPressed: controller.registerTeacher,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to build text fields
|
||||||
|
|
||||||
|
// Helper method to build primary button
|
||||||
|
Widget _buildPrimaryButton({
|
||||||
|
required String label,
|
||||||
|
required IconData icon,
|
||||||
|
required VoidCallback onPressed,
|
||||||
|
required bool isLoading,
|
||||||
|
}) {
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: isLoading ? null : onPressed,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue.shade700,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
minimumSize: const Size(double.infinity, 56),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
disabledBackgroundColor: Colors.blue.shade300,
|
||||||
|
),
|
||||||
|
child: isLoading
|
||||||
|
? const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2.5,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 20),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,693 @@
|
||||||
|
// SplashScreen - Halaman pertama yang muncul dengan animasi dan cek sesi
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/page_student_dashboard.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class SplashScreen extends StatefulWidget {
|
||||||
|
const SplashScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SplashScreen> createState() => _SplashScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SplashScreenState extends State<SplashScreen>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
late AnimationController _mainController;
|
||||||
|
late AnimationController _networkController;
|
||||||
|
late Animation<double> _fadeAnimation;
|
||||||
|
late Animation<double> _scaleAnimation;
|
||||||
|
late Animation<double> _translateAnimation;
|
||||||
|
|
||||||
|
// List of network nodes for neural network visualization
|
||||||
|
final List<NetworkNode> _nodes = [];
|
||||||
|
final List<NetworkConnection> _connections = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
// Create network nodes and connections
|
||||||
|
_setupNetworkGraph();
|
||||||
|
|
||||||
|
// Main controller for logo animations - 3 seconds
|
||||||
|
_mainController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 3000),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Separate slow controller for network animations - 15 seconds and repeat
|
||||||
|
_networkController = AnimationController(
|
||||||
|
duration: const Duration(milliseconds: 15000),
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fade in animation for text
|
||||||
|
_fadeAnimation = Tween<double>(
|
||||||
|
begin: 0.0,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _mainController,
|
||||||
|
curve: const Interval(0.3, 0.8, curve: Curves.easeInOut),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Scale animation for logo - starts very small
|
||||||
|
_scaleAnimation = Tween<double>(
|
||||||
|
begin: 0.1,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _mainController,
|
||||||
|
curve: const Interval(0.1, 0.6, curve: Curves.elasticOut),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Translation animation - starts way below screen
|
||||||
|
_translateAnimation = Tween<double>(
|
||||||
|
begin: 300.0, // Start far below center
|
||||||
|
end: 0.0, // End at center
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: _mainController,
|
||||||
|
curve: const Interval(0.1, 0.6, curve: Curves.easeOutCubic),
|
||||||
|
));
|
||||||
|
|
||||||
|
// Start animations
|
||||||
|
_mainController.forward();
|
||||||
|
_networkController.repeat(); // Continuous very slow animation for network
|
||||||
|
|
||||||
|
// Check user session after animation completes
|
||||||
|
Future.delayed(const Duration(milliseconds: 3500), () {
|
||||||
|
checkExistingSession();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup network graph with nodes and connections
|
||||||
|
void _setupNetworkGraph() {
|
||||||
|
final random = math.Random(42); // Fixed seed for consistent layout
|
||||||
|
|
||||||
|
// Create nodes - spreading them beyond screen boundaries
|
||||||
|
for (int i = 0; i < 25; i++) {
|
||||||
|
// Increased number of nodes
|
||||||
|
_nodes.add(
|
||||||
|
NetworkNode(
|
||||||
|
x: -0.3 +
|
||||||
|
random.nextDouble() *
|
||||||
|
1.6, // Expand beyond screen boundaries (-0.3 to 1.3)
|
||||||
|
y: -0.3 + random.nextDouble() * 1.6,
|
||||||
|
size: 3.0 + random.nextDouble() * 5.0, // Slightly larger nodes
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create connections between nodes - increased connections
|
||||||
|
for (int i = 0; i < _nodes.length; i++) {
|
||||||
|
// Each node connects to 3-6 other nodes
|
||||||
|
final connectionCount = 3 + random.nextInt(4);
|
||||||
|
final connectedIndices = <int>{};
|
||||||
|
|
||||||
|
for (int j = 0; j < connectionCount; j++) {
|
||||||
|
// Try to find a new node to connect to
|
||||||
|
int attempts = 0;
|
||||||
|
while (attempts < 10) {
|
||||||
|
final targetIndex = random.nextInt(_nodes.length);
|
||||||
|
|
||||||
|
// Don't connect to self and don't duplicate connections
|
||||||
|
if (targetIndex != i && !connectedIndices.contains(targetIndex)) {
|
||||||
|
connectedIndices.add(targetIndex);
|
||||||
|
|
||||||
|
// Add the connection
|
||||||
|
_connections.add(
|
||||||
|
NetworkConnection(
|
||||||
|
sourceIndex: i,
|
||||||
|
targetIndex: targetIndex,
|
||||||
|
pulseOffset: random.nextDouble(),
|
||||||
|
pulseSpeed:
|
||||||
|
0.2 + random.nextDouble() * 0.3, // Slightly slower pulse
|
||||||
|
),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> checkExistingSession() async {
|
||||||
|
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||||
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
|
|
||||||
|
try {
|
||||||
|
User? currentUser = _auth.currentUser;
|
||||||
|
if (currentUser != null) {
|
||||||
|
// Get the schoolId from shared preferences
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
final schoolId = prefs.getString('school_id');
|
||||||
|
|
||||||
|
if (schoolId != null && schoolId.isNotEmpty) {
|
||||||
|
// Check if user data exists in the specific school
|
||||||
|
final docSnapshot = await _firestore
|
||||||
|
.collection('schools')
|
||||||
|
.doc(schoolId)
|
||||||
|
.collection('students')
|
||||||
|
.doc(currentUser.uid)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (docSnapshot.exists) {
|
||||||
|
// Navigate to dashboard
|
||||||
|
Get.offAll(() => const PageStudentDashboard());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no school ID found or user not found in that school, search all schools
|
||||||
|
final schoolsSnapshot = await _firestore.collection('schools').get();
|
||||||
|
bool foundInAnySchool = false;
|
||||||
|
|
||||||
|
for (var schoolDoc in schoolsSnapshot.docs) {
|
||||||
|
final studentDoc = await schoolDoc.reference
|
||||||
|
.collection('students')
|
||||||
|
.doc(currentUser.uid)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (studentDoc.exists) {
|
||||||
|
// Found student in this school, save the school ID
|
||||||
|
await prefs.setString('school_id', schoolDoc.id);
|
||||||
|
foundInAnySchool = true;
|
||||||
|
|
||||||
|
// Navigate to dashboard
|
||||||
|
Get.offAll(() => const PageStudentDashboard());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!foundInAnySchool) {
|
||||||
|
// User authenticated but no profile found in any school
|
||||||
|
Get.offAll(() => const IntroPage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// User not logged in, go to intro
|
||||||
|
Get.offAll(() => const IntroPage());
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Error checking session: $e');
|
||||||
|
// If error, go to intro
|
||||||
|
Get.offAll(() => const IntroPage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_mainController.dispose();
|
||||||
|
_networkController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
// Beautiful gradient background
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [
|
||||||
|
Colors.blue.shade800,
|
||||||
|
Colors.indigo.shade900,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Grid background pattern
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _networkController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: GridPatternPainter(
|
||||||
|
progress: _networkController.value,
|
||||||
|
),
|
||||||
|
size: Size.infinite,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// Neural network background - using CustomPaint for better performance
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _networkController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: NetworkPainter(
|
||||||
|
progress: _networkController.value,
|
||||||
|
nodes: _nodes,
|
||||||
|
connections: _connections,
|
||||||
|
),
|
||||||
|
size: Size.infinite,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// Subtle wave pattern in background
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _networkController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: WavePatternPainter(
|
||||||
|
progress: _networkController.value,
|
||||||
|
),
|
||||||
|
size: Size.infinite,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// Main content - centered column
|
||||||
|
Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
// Logo with animation from bottom to center with scaling
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _mainController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.translate(
|
||||||
|
offset: Offset(0, _translateAnimation.value),
|
||||||
|
child: Transform.scale(
|
||||||
|
scale: _scaleAnimation.value,
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 150,
|
||||||
|
height: 150,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blue.shade700.withOpacity(0.6),
|
||||||
|
blurRadius: 20,
|
||||||
|
spreadRadius: 5,
|
||||||
|
offset: const Offset(0, 5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
// Brain icon with glow effect
|
||||||
|
Container(
|
||||||
|
width: 110,
|
||||||
|
height: 110,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade100.withOpacity(0.2),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
Icons.psychology,
|
||||||
|
size: 95,
|
||||||
|
color: Colors.blue.shade700,
|
||||||
|
),
|
||||||
|
// Animated glow around the icon
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _networkController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: GlowingCirclePainter(
|
||||||
|
progress: _networkController.value,
|
||||||
|
color: Colors.blue.shade500,
|
||||||
|
),
|
||||||
|
size: const Size(120, 120),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
// App title with fade animation
|
||||||
|
FadeTransition(
|
||||||
|
opacity: _fadeAnimation,
|
||||||
|
child: Text(
|
||||||
|
'EduGuide',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
letterSpacing: 1.5,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black54,
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Subtitle with fade animation
|
||||||
|
FadeTransition(
|
||||||
|
opacity: _fadeAnimation,
|
||||||
|
child: Text(
|
||||||
|
'Rekomendasi Minat Bakat Anda',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w300,
|
||||||
|
color: Colors.white.withOpacity(0.8),
|
||||||
|
letterSpacing: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 40),
|
||||||
|
|
||||||
|
// Loading indicator with delayed fade
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _mainController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Opacity(
|
||||||
|
opacity: _mainController.value > 0.5
|
||||||
|
? (_mainController.value - 0.5) * 2
|
||||||
|
: 0,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Colors.white.withOpacity(0.9)),
|
||||||
|
strokeWidth: 3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node in the network
|
||||||
|
class NetworkNode {
|
||||||
|
final double x; // Position (0-1)
|
||||||
|
final double y;
|
||||||
|
final double size;
|
||||||
|
|
||||||
|
NetworkNode({
|
||||||
|
required this.x,
|
||||||
|
required this.y,
|
||||||
|
required this.size,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection between nodes
|
||||||
|
class NetworkConnection {
|
||||||
|
final int sourceIndex;
|
||||||
|
final int targetIndex;
|
||||||
|
final double pulseOffset; // Random offset for animation
|
||||||
|
final double pulseSpeed; // Speed of pulse animation
|
||||||
|
|
||||||
|
NetworkConnection({
|
||||||
|
required this.sourceIndex,
|
||||||
|
required this.targetIndex,
|
||||||
|
required this.pulseOffset,
|
||||||
|
required this.pulseSpeed,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neural network visualization
|
||||||
|
class NetworkPainter extends CustomPainter {
|
||||||
|
final double progress;
|
||||||
|
final List<NetworkNode> nodes;
|
||||||
|
final List<NetworkConnection> connections;
|
||||||
|
|
||||||
|
NetworkPainter({
|
||||||
|
required this.progress,
|
||||||
|
required this.nodes,
|
||||||
|
required this.connections,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
// Draw connections first (lines between nodes)
|
||||||
|
for (final connection in connections) {
|
||||||
|
final source = nodes[connection.sourceIndex];
|
||||||
|
final target = nodes[connection.targetIndex];
|
||||||
|
|
||||||
|
final sourcePos = Offset(source.x * size.width, source.y * size.height);
|
||||||
|
final targetPos = Offset(target.x * size.width, target.y * size.height);
|
||||||
|
|
||||||
|
// Calculate distance for line dashing
|
||||||
|
final dx = targetPos.dx - sourcePos.dx;
|
||||||
|
final dy = targetPos.dy - sourcePos.dy;
|
||||||
|
final distance = math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
// Create a normalized direction vector
|
||||||
|
final dirX = dx / distance;
|
||||||
|
final dirY = dy / distance;
|
||||||
|
|
||||||
|
// Calculate pulse position based on progress
|
||||||
|
final pulseProgress =
|
||||||
|
(progress * connection.pulseSpeed + connection.pulseOffset) % 1.0;
|
||||||
|
final pulsePos = Offset(
|
||||||
|
sourcePos.dx + dx * pulseProgress,
|
||||||
|
sourcePos.dy + dy * pulseProgress,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw the connection line
|
||||||
|
final linePaint = Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.15)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.2;
|
||||||
|
|
||||||
|
canvas.drawLine(sourcePos, targetPos, linePaint);
|
||||||
|
|
||||||
|
// Draw pulse traveling along the connection
|
||||||
|
final pulsePaint = Paint()
|
||||||
|
..color = Colors.blue.shade100.withOpacity(0.5)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
canvas.drawCircle(pulsePos, 2.5, pulsePaint);
|
||||||
|
|
||||||
|
// Add a subtle glow around the pulse
|
||||||
|
final glowPaint = Paint()
|
||||||
|
..color = Colors.blue.shade100.withOpacity(0.2)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
canvas.drawCircle(pulsePos, 5.0, glowPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw nodes
|
||||||
|
for (final node in nodes) {
|
||||||
|
final nodePos = Offset(node.x * size.width, node.y * size.height);
|
||||||
|
|
||||||
|
// Node glow (outer circle)
|
||||||
|
final glowPaint = Paint()
|
||||||
|
..color = Colors.blue.shade200.withOpacity(0.2)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
canvas.drawCircle(nodePos, node.size * 1.8, glowPaint);
|
||||||
|
|
||||||
|
// Node main circle
|
||||||
|
final nodePaint = Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.7)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
canvas.drawCircle(nodePos, node.size, nodePaint);
|
||||||
|
|
||||||
|
// Node inner circle
|
||||||
|
final innerPaint = Paint()
|
||||||
|
..color = Colors.blue.shade100
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
canvas.drawCircle(nodePos, node.size * 0.6, innerPaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(NetworkPainter oldDelegate) {
|
||||||
|
return oldDelegate.progress != progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Glowing circle around the icon
|
||||||
|
class GlowingCirclePainter extends CustomPainter {
|
||||||
|
final double progress;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
GlowingCirclePainter({required this.progress, required this.color});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final center = Offset(size.width / 2, size.height / 2);
|
||||||
|
|
||||||
|
// Pulsing circular glow - very subtle
|
||||||
|
final double pulseSize = 1.0 + math.sin(progress * math.pi) * 0.08;
|
||||||
|
|
||||||
|
// Draw multiple circles with diminishing opacity
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = color.withOpacity(0.2 - (i * 0.05))
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.0 - (i * 0.5);
|
||||||
|
|
||||||
|
// Each circle is larger than the previous
|
||||||
|
canvas.drawCircle(
|
||||||
|
center,
|
||||||
|
(50 + i * 5) * pulseSize,
|
||||||
|
paint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw spinning arc - very slow
|
||||||
|
final spinnerPaint = Paint()
|
||||||
|
..color = color.withOpacity(0.7)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.0;
|
||||||
|
|
||||||
|
// Rotation angle changes very slowly with progress
|
||||||
|
final startAngle = progress * math.pi;
|
||||||
|
const arcLength = math.pi * 1.2; // Longer arc
|
||||||
|
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromCircle(center: center, radius: 55),
|
||||||
|
startAngle,
|
||||||
|
arcLength,
|
||||||
|
false,
|
||||||
|
spinnerPaint,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Second arc in opposite direction - even slower
|
||||||
|
canvas.drawArc(
|
||||||
|
Rect.fromCircle(center: center, radius: 45),
|
||||||
|
-startAngle * 0.7,
|
||||||
|
-arcLength * 0.8,
|
||||||
|
false,
|
||||||
|
spinnerPaint..strokeWidth = 1.5,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(GlowingCirclePainter oldDelegate) {
|
||||||
|
return oldDelegate.progress != progress || oldDelegate.color != color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subtle wave pattern in background
|
||||||
|
class WavePatternPainter extends CustomPainter {
|
||||||
|
final double progress;
|
||||||
|
|
||||||
|
WavePatternPainter({required this.progress});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
// Very subtle horizontal waves
|
||||||
|
final wavePaint = Paint()
|
||||||
|
..color = Colors.blue.shade200.withOpacity(0.03)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.0;
|
||||||
|
|
||||||
|
const waveCount = 4;
|
||||||
|
final waveHeight = size.height / waveCount;
|
||||||
|
|
||||||
|
for (int i = 0; i < waveCount; i++) {
|
||||||
|
final path = Path();
|
||||||
|
final baseY = i * waveHeight;
|
||||||
|
final amplitude = 8.0; // Reduced wave height
|
||||||
|
|
||||||
|
path.moveTo(0, baseY);
|
||||||
|
|
||||||
|
// Draw a smooth sine wave across the screen - very slow movement
|
||||||
|
for (double x = 0; x <= size.width; x += 10) {
|
||||||
|
// Very slow wave movement (reduced multiplier from 0.5 to 0.2)
|
||||||
|
final waveY = baseY +
|
||||||
|
math.sin((x / size.width * 4) + progress * math.pi * 0.2) *
|
||||||
|
amplitude;
|
||||||
|
path.lineTo(x, waveY);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawPath(path, wavePaint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(WavePatternPainter oldDelegate) {
|
||||||
|
return oldDelegate.progress != progress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grid background pattern
|
||||||
|
class GridPatternPainter extends CustomPainter {
|
||||||
|
final double progress;
|
||||||
|
|
||||||
|
GridPatternPainter({required this.progress});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = Colors.white.withOpacity(0.05)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 0.8;
|
||||||
|
|
||||||
|
// Create animated grid pattern
|
||||||
|
final spacing = 35.0;
|
||||||
|
final xCount = (size.width / spacing).ceil() + 1;
|
||||||
|
final yCount = (size.height / spacing).ceil() + 1;
|
||||||
|
final offset = progress * spacing * 0.5; // Slow movement
|
||||||
|
|
||||||
|
// Horizontal lines
|
||||||
|
for (int i = 0; i < yCount; i++) {
|
||||||
|
final y = i * spacing - offset;
|
||||||
|
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical lines
|
||||||
|
for (int i = 0; i < xCount; i++) {
|
||||||
|
final x = i * spacing - offset;
|
||||||
|
canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some diagonal lines for more depth
|
||||||
|
final diagonalPaint = Paint()
|
||||||
|
..color = Colors.blue.shade100.withOpacity(0.04)
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 0.7;
|
||||||
|
|
||||||
|
final maxDim = math.max(size.width, size.height);
|
||||||
|
final diagonalSpacing = 70.0;
|
||||||
|
final diagCount = (maxDim / diagonalSpacing).ceil() * 2;
|
||||||
|
final diagOffset = progress * diagonalSpacing * 0.3; // Very slow movement
|
||||||
|
|
||||||
|
for (int i = -diagCount; i < diagCount; i++) {
|
||||||
|
final startX = i * diagonalSpacing + diagOffset;
|
||||||
|
canvas.drawLine(
|
||||||
|
Offset(startX, 0),
|
||||||
|
Offset(startX + maxDim, maxDim),
|
||||||
|
diagonalPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(GridPatternPainter oldDelegate) {
|
||||||
|
return oldDelegate.progress != progress;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:forward_chaining_man_app/app/controllers/developer_controller.dart';
|
||||||
|
import 'package:get/get_rx/src/rx_types/rx_types.dart';
|
||||||
|
import 'package:get/get_state_manager/src/simple/get_controllers.dart';
|
||||||
|
|
||||||
|
/// Controller untuk DeveloperModePage
|
||||||
|
class DeveloperModeController extends GetxController {
|
||||||
|
final RxBool isDeveloperMode = developerMode.obs;
|
||||||
|
|
||||||
|
void toggleDeveloperMode(bool value) {
|
||||||
|
isDeveloperMode.value = value;
|
||||||
|
developerMode = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,677 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart' as rootBundle;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:forward_chaining_man_app/admin_mode.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/about/page_about.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_intro.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_login.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/page_profile.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/controller/question_controller.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/page_question.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/page_select_major.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/widget/wave_clipper.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/feature/recomendation_screen/view/page_recomendation_screen.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/model/data_student.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
// Helper function to replace the simple dialog with our new UI
|
||||||
|
void showRecommendationResultsGetx(RecommendationResult result,
|
||||||
|
{String rawMessage = ''}) {
|
||||||
|
Get.to(() => RecommendationResultsScreen(
|
||||||
|
result: result,
|
||||||
|
rawMessage: rawMessage,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomeController extends GetxController {
|
||||||
|
final Rx<bool?> pilihan =
|
||||||
|
Rx<bool?>(null); // null=belum pilih; true=Kerja; false=Kuliah
|
||||||
|
final RxString selectedKode =
|
||||||
|
"".obs; // Menyimpan kode pilihan yang dipilih user
|
||||||
|
|
||||||
|
// Step tracking - new variable to track current selection step
|
||||||
|
final RxInt currentStep = 0.obs; // 0=Pilih kondisi ekonomi, 1=Pilih rencana
|
||||||
|
|
||||||
|
// Variable to track selected economic condition
|
||||||
|
final RxString selectedEconomicCondition = "".obs; // "CUKUP" or "TERBATAS"
|
||||||
|
|
||||||
|
// Dapatkan preferensi minat dari controller sebelumnya
|
||||||
|
final majorPrefController = Get.find<MajorPreferenceController>();
|
||||||
|
|
||||||
|
// Method to select economic condition in step 1
|
||||||
|
void setEconomicCondition(String condition) {
|
||||||
|
selectedEconomicCondition.value = condition;
|
||||||
|
// Move to next step
|
||||||
|
currentStep.value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to select plan in step 2
|
||||||
|
void setPilihan(String kode) {
|
||||||
|
if (selectedKode.value == kode)
|
||||||
|
return; // Jika memilih yang sama, tidak berubah
|
||||||
|
selectedKode.value = kode;
|
||||||
|
|
||||||
|
// Logika pemilihan: Kuliah atau Kerja
|
||||||
|
if (kode == "E01" || kode == "E02" || kode == "E03") {
|
||||||
|
pilihan.value = false; // Kuliah
|
||||||
|
} else if (kode == "E04" || kode == "E05") {
|
||||||
|
pilihan.value = true; // Kerja
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to go back to step 1
|
||||||
|
void goBackToStep1() {
|
||||||
|
currentStep.value = 0;
|
||||||
|
selectedKode.value = "";
|
||||||
|
pilihan.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tambahkan getter untuk memudahkan akses preferensi minat
|
||||||
|
String get selectedMajor => majorPrefController.selectedMajor.value;
|
||||||
|
bool get isSainsMajor => selectedMajor == "SAINS";
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePage extends StatelessWidget {
|
||||||
|
const HomePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final controller = Get.put(HomeController());
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.blue.shade800,
|
||||||
|
Colors.indigo.shade900,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Custom App Bar
|
||||||
|
SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
// If on step 2, go back to step 1, otherwise go back to previous page
|
||||||
|
if (controller.currentStep.value == 1) {
|
||||||
|
controller.goBackToStep1();
|
||||||
|
} else {
|
||||||
|
Get.back();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_back,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Obx(() => Text(
|
||||||
|
controller.currentStep.value == 0
|
||||||
|
? 'Pilih Kondisi Ekonomi'
|
||||||
|
: 'Pilih Rencana Anda',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Main Content Area
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 5),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(30),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
|
||||||
|
child: Obx(() {
|
||||||
|
if (controller.currentStep.value == 0) {
|
||||||
|
// STEP 1: Choose Economic Condition
|
||||||
|
return _buildEconomicConditionStep(controller);
|
||||||
|
} else {
|
||||||
|
// STEP 2: Choose Plan based on Economic Condition
|
||||||
|
return _buildPlanSelectionStep(controller, context);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fungsi untuk menampilkan dialog konfirmasi
|
||||||
|
void _showConfirmationDialog(BuildContext context, VoidCallback onConfirm) {
|
||||||
|
showGeneralDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
barrierLabel: 'Konfirmasi',
|
||||||
|
transitionDuration: const Duration(milliseconds: 300),
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) {
|
||||||
|
return Container(); // Tidak digunakan, kita menggunakan transitionBuilder
|
||||||
|
},
|
||||||
|
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
final curvedAnimation = CurvedAnimation(
|
||||||
|
parent: animation,
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: Tween<double>(begin: 0.8, end: 1.0).animate(curvedAnimation),
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity:
|
||||||
|
Tween<double>(begin: 0.0, end: 1.0).animate(curvedAnimation),
|
||||||
|
child: Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
insetPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: ConfirmationDialogContent(onConfirm: onConfirm),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 1: Widget for Economic Condition Selection
|
||||||
|
Widget _buildEconomicConditionStep(HomeController controller) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.account_balance_wallet_outlined,
|
||||||
|
size: 24,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Kondisi Ekonomi',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Pilih situasi yang paling sesuai',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
|
||||||
|
// Economic Condition Cards
|
||||||
|
buildEconomicConditionCard(
|
||||||
|
title: "Kondisi ekonomi cukup untuk kuliah",
|
||||||
|
subtitle: "Memiliki dana untuk pendidikan lanjutan",
|
||||||
|
condition: "CUKUP",
|
||||||
|
icon: Icons.school,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
buildEconomicConditionCard(
|
||||||
|
title: "Kondisi ekonomi terbatas",
|
||||||
|
subtitle: "Perlu mempertimbangkan berbagai pilihan",
|
||||||
|
condition: "TERBATAS",
|
||||||
|
icon: Icons.attach_money,
|
||||||
|
controller: controller,
|
||||||
|
),
|
||||||
|
|
||||||
|
const Spacer(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 2: Widget for Plan Selection based on Economic Condition
|
||||||
|
Widget _buildPlanSelectionStep(
|
||||||
|
HomeController controller, BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.lightbulb_outline,
|
||||||
|
size: 24,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
controller.selectedEconomicCondition.value == "CUKUP"
|
||||||
|
? 'Dengan Ekonomi Cukup'
|
||||||
|
: 'Dengan Ekonomi Terbatas',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Pilih rencana yang paling sesuai',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Options List
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Show relevant options based on economic condition
|
||||||
|
if (controller.selectedEconomicCondition.value == "CUKUP") ...[
|
||||||
|
// Options for sufficient economic condition
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Kuliah",
|
||||||
|
subtitle: "Melanjutkan pendidikan ke perguruan tinggi",
|
||||||
|
kode: "E01",
|
||||||
|
icon: Icons.school,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Mencari beasiswa",
|
||||||
|
subtitle: "Kuliah dengan bantuan biaya pendidikan",
|
||||||
|
kode: "E03",
|
||||||
|
icon: Icons.card_giftcard,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Memilih bekerja atau usaha",
|
||||||
|
subtitle: "Langsung terjun ke dunia kerja",
|
||||||
|
kode: "E04",
|
||||||
|
icon: Icons.work,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Bekerja dulu, kuliah nanti",
|
||||||
|
subtitle: "Menunda kuliah untuk bekerja",
|
||||||
|
kode: "E05",
|
||||||
|
icon: Icons.timeline,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
] else ...[
|
||||||
|
// Options for limited economic condition
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Mencari beasiswa",
|
||||||
|
subtitle: "Kuliah dengan bantuan biaya pendidikan",
|
||||||
|
kode: "E03",
|
||||||
|
icon: Icons.card_giftcard,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Kuliah dengan biaya terjangkau",
|
||||||
|
subtitle: "Memilih perguruan tinggi yang ekonomis",
|
||||||
|
kode: "E02",
|
||||||
|
icon: Icons.school,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Memilih bekerja atau usaha",
|
||||||
|
subtitle: "Langsung terjun ke dunia kerja",
|
||||||
|
kode: "E04",
|
||||||
|
icon: Icons.work,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
Obx(() => buildOptionCard(
|
||||||
|
title: "Bekerja dulu, kuliah nanti",
|
||||||
|
subtitle: "Menunda kuliah untuk bekerja",
|
||||||
|
kode: "E05",
|
||||||
|
icon: Icons.timeline,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Add some space at the bottom for better scrolling
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Next Button
|
||||||
|
Obx(() => AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
opacity: controller.selectedKode.value.isEmpty ? 0.6 : 1.0,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow: controller.selectedKode.value.isEmpty
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blue.shade300.withOpacity(0.4),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: controller.selectedKode.value.isEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
// Munculkan dialog konfirmasi terlebih dahulu
|
||||||
|
_showConfirmationDialog(context, () {
|
||||||
|
// Callback ini akan dipanggil ketika user menekan tombol "Ya, Lanjutkan"
|
||||||
|
// Buat instance QuestionController dengan preferensi yang sesuai
|
||||||
|
final questionController =
|
||||||
|
Get.put(QuestionController(
|
||||||
|
isKerja: controller.pilihan.value!,
|
||||||
|
majorType: controller.selectedMajor,
|
||||||
|
));
|
||||||
|
|
||||||
|
questionController.clearQuestion();
|
||||||
|
questionController.loadProgramData(
|
||||||
|
controller.pilihan.value!,
|
||||||
|
controller.selectedMajor);
|
||||||
|
|
||||||
|
// Navigasi ke halaman pertanyaan
|
||||||
|
Get.to(() => QuestionPage(
|
||||||
|
isKerja: controller.pilihan.value!,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue.shade600,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
minimumSize: const Size(double.infinity, 54),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
disabledBackgroundColor: Colors.blue.shade200,
|
||||||
|
disabledForegroundColor: Colors.white70,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
controller.selectedKode.value.isEmpty
|
||||||
|
? 'Pilih Salah Satu Opsi'
|
||||||
|
: 'Lanjutkan',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(Icons.arrow_forward, size: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget for economic condition selection card (Step 1)
|
||||||
|
Widget buildEconomicConditionCard({
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required String condition,
|
||||||
|
required IconData icon,
|
||||||
|
required HomeController controller,
|
||||||
|
}) {
|
||||||
|
return Container(
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => controller.setEconomicCondition(condition),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 30,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.arrow_forward_ios,
|
||||||
|
color: Colors.blue.shade300,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widget untuk membuat tampilan pilihan lebih menarik dan modern (Step 2)
|
||||||
|
Widget buildOptionCard({
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required String kode,
|
||||||
|
required IconData icon,
|
||||||
|
required HomeController controller,
|
||||||
|
}) {
|
||||||
|
final isSelected = controller.selectedKode.value == kode;
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 12),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => controller.setPilihan(kode),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: isSelected ? Colors.blue.shade50 : Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? Colors.blue.shade400 : Colors.grey.shade200,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
blurRadius: isSelected ? 8 : 4,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.shade400
|
||||||
|
: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 24,
|
||||||
|
color: isSelected ? Colors.white : Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.shade800
|
||||||
|
: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.shade600
|
||||||
|
: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
opacity: isSelected ? 1.0 : 0.0,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade400,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,354 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:forward_chaining_man_app/app/views/student/feature/quiz/view/page_select_economy.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class MajorPreferenceController extends GetxController {
|
||||||
|
final RxString selectedMajor = "".obs;
|
||||||
|
|
||||||
|
void setMajor(String major) {
|
||||||
|
if (selectedMajor.value == major) return;
|
||||||
|
selectedMajor.value = major;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MajorPreferencePage extends StatelessWidget {
|
||||||
|
const MajorPreferencePage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final controller = Get.put(MajorPreferenceController());
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
body: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.blue.shade800,
|
||||||
|
Colors.indigo.shade900,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// Custom App Bar
|
||||||
|
SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white.withOpacity(0.2),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => Get.back(),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.arrow_back,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
const Text(
|
||||||
|
'Pilih Minat Anda',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Main Content Area
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(top: 24),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.vertical(
|
||||||
|
top: Radius.circular(30),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 30, 20, 20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
Icons.school_outlined,
|
||||||
|
size: 24,
|
||||||
|
color: Colors.blue.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Minat IPA',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'Pilih bidang yang paling Anda minati',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// Options List
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
// IPA Sains Option
|
||||||
|
Obx(() => buildMajorCard(
|
||||||
|
title: "IPA (Sains Murni)",
|
||||||
|
subtitle:
|
||||||
|
"Fokus: Biologi, Kimia, Fisika (kedokteran, farmasi, sains)",
|
||||||
|
description:
|
||||||
|
"Pilihan yang tepat jika Anda tertarik pada ilmu-ilmu murni seperti biologi, kimia, dan fisika. Ideal untuk karir di bidang kedokteran, farmasi, penelitian ilmiah, atau bidang kesehatan lainnya.",
|
||||||
|
kode: "SAINS",
|
||||||
|
icon: Icons.biotech,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// IPA Teknik Option
|
||||||
|
Obx(() => buildMajorCard(
|
||||||
|
title: "IPA (Teknik)",
|
||||||
|
subtitle:
|
||||||
|
"Fokus: Matematika, Fisika, IT (arah teknik/teknologi)",
|
||||||
|
description:
|
||||||
|
"Cocok jika Anda memiliki minat kuat di bidang matematika, fisika terapan, dan teknologi informasi. Ideal untuk karir di bidang teknik, IT, rekayasa, atau arsitektur.",
|
||||||
|
kode: "TEKNIK",
|
||||||
|
icon: Icons.engineering,
|
||||||
|
controller: controller,
|
||||||
|
)),
|
||||||
|
|
||||||
|
// Add some space at the bottom for better scrolling
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Next Button
|
||||||
|
Obx(() => AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
opacity: controller.selectedMajor.value.isEmpty
|
||||||
|
? 0.6
|
||||||
|
: 1.0,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
boxShadow:
|
||||||
|
controller.selectedMajor.value.isEmpty
|
||||||
|
? []
|
||||||
|
: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.blue.shade300
|
||||||
|
.withOpacity(0.4),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 6),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed:
|
||||||
|
controller.selectedMajor.value.isEmpty
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
// Navigate to economic preference page
|
||||||
|
Get.to(() => HomePage());
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue.shade600,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 16),
|
||||||
|
minimumSize: const Size(double.infinity, 54),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
disabledBackgroundColor: Colors.blue.shade200,
|
||||||
|
disabledForegroundColor: Colors.white70,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
controller.selectedMajor.value.isEmpty
|
||||||
|
? 'Pilih Salah Satu Minat'
|
||||||
|
: 'Lanjutkan',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
const Icon(Icons.arrow_forward, size: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Widget untuk membuat card pilihan minat
|
||||||
|
Widget buildMajorCard({
|
||||||
|
required String title,
|
||||||
|
required String subtitle,
|
||||||
|
required String description,
|
||||||
|
required String kode,
|
||||||
|
required IconData icon,
|
||||||
|
required MajorPreferenceController controller,
|
||||||
|
}) {
|
||||||
|
final isSelected = controller.selectedMajor.value == kode;
|
||||||
|
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => controller.setMajor(kode),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: isSelected ? Colors.blue.shade50 : Colors.white,
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected ? Colors.blue.shade400 : Colors.grey.shade200,
|
||||||
|
width: 2,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.shade200,
|
||||||
|
blurRadius: isSelected ? 8 : 4,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.shade400
|
||||||
|
: Colors.grey.shade100,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 24,
|
||||||
|
color: isSelected ? Colors.white : Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.shade800
|
||||||
|
: Colors.black87,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
subtitle,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.blue.shade600
|
||||||
|
: Colors.grey.shade600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
opacity: isSelected ? 1.0 : 0.0,
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade400,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text(
|
||||||
|
description,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color:
|
||||||
|
isSelected ? Colors.blue.shade700 : Colors.grey.shade700,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class ShimmerEffect extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
final Color baseColor;
|
||||||
|
final Color highlightColor;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
const ShimmerEffect({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
this.baseColor = Colors.white54,
|
||||||
|
this.highlightColor = Colors.white,
|
||||||
|
this.duration = const Duration(seconds: 2),
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ShimmerEffect> createState() => _ShimmerEffectState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShimmerEffectState extends State<ShimmerEffect>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _controller;
|
||||||
|
late Animation<double> _animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: widget.duration,
|
||||||
|
)..repeat();
|
||||||
|
|
||||||
|
_animation = Tween<double>(begin: -1.0, end: 2.0).animate(
|
||||||
|
CurvedAnimation(parent: _controller, curve: Curves.easeInOutSine),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _animation,
|
||||||
|
builder: (BuildContext context, Widget? child) {
|
||||||
|
return ShaderMask(
|
||||||
|
blendMode: BlendMode.srcIn,
|
||||||
|
shaderCallback: (bounds) {
|
||||||
|
return LinearGradient(
|
||||||
|
colors: [
|
||||||
|
widget.baseColor,
|
||||||
|
widget.highlightColor,
|
||||||
|
widget.baseColor,
|
||||||
|
],
|
||||||
|
stops: const [0.0, 0.5, 1.0],
|
||||||
|
begin: Alignment.centerLeft,
|
||||||
|
end: Alignment.centerRight,
|
||||||
|
transform: _SlidingGradientTransform(
|
||||||
|
slidePercent: _animation.value,
|
||||||
|
),
|
||||||
|
).createShader(bounds);
|
||||||
|
},
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SlidingGradientTransform extends GradientTransform {
|
||||||
|
const _SlidingGradientTransform({
|
||||||
|
required this.slidePercent,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double slidePercent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Matrix4? transform(Rect bounds, {TextDirection? textDirection}) {
|
||||||
|
return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,378 @@
|
||||||
|
// Widget untuk animasi gelombang di bagian bawah dialog
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
// Fungsi untuk menampilkan dialog konfirmasi
|
||||||
|
void _showConfirmationDialog(BuildContext context, VoidCallback onConfirm) {
|
||||||
|
showGeneralDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: true,
|
||||||
|
barrierLabel: 'Konfirmasi',
|
||||||
|
transitionDuration: const Duration(milliseconds: 300),
|
||||||
|
pageBuilder: (context, animation, secondaryAnimation) {
|
||||||
|
return Container(); // Tidak digunakan, kita menggunakan transitionBuilder
|
||||||
|
},
|
||||||
|
transitionBuilder: (context, animation, secondaryAnimation, child) {
|
||||||
|
final curvedAnimation = CurvedAnimation(
|
||||||
|
parent: animation,
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
);
|
||||||
|
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: Tween<double>(begin: 0.8, end: 1.0).animate(curvedAnimation),
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: Tween<double>(begin: 0.0, end: 1.0).animate(curvedAnimation),
|
||||||
|
child: Dialog(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
insetPadding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: ConfirmationDialogContent(onConfirm: onConfirm),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget konten dialog
|
||||||
|
class ConfirmationDialogContent extends StatefulWidget {
|
||||||
|
final VoidCallback onConfirm;
|
||||||
|
|
||||||
|
const ConfirmationDialogContent({
|
||||||
|
Key? key,
|
||||||
|
required this.onConfirm,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ConfirmationDialogContent> createState() =>
|
||||||
|
_ConfirmationDialogContentState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConfirmationDialogContentState extends State<ConfirmationDialogContent>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
late AnimationController _waveController;
|
||||||
|
final List<bool> _checkedItems = [false, false, false];
|
||||||
|
bool get _allChecked => _checkedItems.every((element) => element);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_waveController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
)..repeat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_waveController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: 400,
|
||||||
|
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
children: [
|
||||||
|
// Background dengan gradien dan gelombang
|
||||||
|
Positioned.fill(
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: _waveController,
|
||||||
|
builder: (context, child) {
|
||||||
|
return ClipPath(
|
||||||
|
clipper: WaveClipper(_waveController.value),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topCenter,
|
||||||
|
end: Alignment.bottomCenter,
|
||||||
|
colors: [
|
||||||
|
Colors.blue.shade50,
|
||||||
|
Colors.blue.shade100,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Konten utama
|
||||||
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
// Header
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade600,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(20),
|
||||||
|
topRight: Radius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.check_circle,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 48,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const Text(
|
||||||
|
'Konfirmasi Pemilihan',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Text(
|
||||||
|
'Pastikan pilihan Anda sudah sesuai sebelum melanjutkan ke kuisioner',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white.withOpacity(0.9),
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Checklist konfirmasi
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 24, 20, 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'Silakan konfirmasi bahwa Anda:',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Checklist pertama
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _checkedItems[0],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_checkedItems[0] = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
title: const Text(
|
||||||
|
'Telah memilih minat yang benar-benar sesuai dengan diri Anda',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
activeColor: Colors.blue.shade600,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
dense: true,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Checklist kedua
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _checkedItems[1],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_checkedItems[1] = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
title: const Text(
|
||||||
|
'Mempertimbangkan kondisi ekonomi keluarga dalam membuat pilihan',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
activeColor: Colors.blue.shade600,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
dense: true,
|
||||||
|
),
|
||||||
|
|
||||||
|
// Checklist ketiga
|
||||||
|
CheckboxListTile(
|
||||||
|
value: _checkedItems[2],
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_checkedItems[2] = value ?? false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
title: const Text(
|
||||||
|
'Memiliki rencana yang jelas untuk masa depan Anda',
|
||||||
|
style: TextStyle(fontSize: 14),
|
||||||
|
),
|
||||||
|
activeColor: Colors.blue.shade600,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
dense: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Pesan tentang kejujuran
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.amber.shade50,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(color: Colors.amber.shade200),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.info_outline,
|
||||||
|
color: Colors.amber.shade800,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'Di halaman selanjutnya Anda akan menjawab beberapa pertanyaan. Pastikan untuk menjawab dengan jujur sesuai kondisi Anda.',
|
||||||
|
style: TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Tombol-tombol
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Tombol Kembali
|
||||||
|
Expanded(
|
||||||
|
child: OutlinedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
style: OutlinedButton.styleFrom(
|
||||||
|
foregroundColor: Colors.blue.shade700,
|
||||||
|
side: BorderSide(color: Colors.blue.shade300),
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text('Kembali'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
// Tombol Lanjutkan
|
||||||
|
Expanded(
|
||||||
|
flex: 2,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: _allChecked
|
||||||
|
? () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
widget.onConfirm();
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.blue.shade600,
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
elevation: 2,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(30),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Ya, Lanjutkan',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Lingkaran dekoratif di pojok
|
||||||
|
Positioned(
|
||||||
|
right: -15,
|
||||||
|
top: -15,
|
||||||
|
child: Container(
|
||||||
|
height: 60,
|
||||||
|
width: 60,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade400.withOpacity(0.3),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: -10,
|
||||||
|
bottom: -10,
|
||||||
|
child: Container(
|
||||||
|
height: 40,
|
||||||
|
width: 40,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.blue.shade400.withOpacity(0.3),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WaveClipper extends CustomClipper<Path> {
|
||||||
|
final double animation;
|
||||||
|
|
||||||
|
WaveClipper(this.animation);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Path getClip(Size size) {
|
||||||
|
final path = Path();
|
||||||
|
final height = size.height;
|
||||||
|
final width = size.width;
|
||||||
|
|
||||||
|
path.lineTo(0, height * 0.7);
|
||||||
|
|
||||||
|
// Buat gelombang dengan animasi
|
||||||
|
final firstControlPoint = Offset(
|
||||||
|
width * 0.25, height * (0.7 + 0.04 * math.sin(animation * math.pi)));
|
||||||
|
final firstEndPoint = Offset(width * 0.5, height * 0.7);
|
||||||
|
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
|
||||||
|
firstEndPoint.dx, firstEndPoint.dy);
|
||||||
|
|
||||||
|
final secondControlPoint = Offset(
|
||||||
|
width * 0.75, height * (0.7 - 0.04 * math.sin(animation * math.pi)));
|
||||||
|
final secondEndPoint = Offset(width, height * 0.7);
|
||||||
|
path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
|
||||||
|
secondEndPoint.dx, secondEndPoint.dy);
|
||||||
|
|
||||||
|
path.lineTo(width, 0);
|
||||||
|
path.close();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReclip(covariant CustomClipper oldClipper) => true;
|
||||||
|
}
|