Compare commits
No commits in common. "177b315f9553c95e6ddbe5a5ae2767f729bd91c0" and "757dfad4e8c6ea2fe57614f16eb6718111c38c15" have entirely different histories.
177b315f95
...
757dfad4e8
|
|
@ -1,79 +1,43 @@
|
||||||
import java.util.Properties
|
|
||||||
import java.io.FileInputStream
|
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
|
id("kotlin-android")
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
id("com.google.gms.google-services")
|
id("com.google.gms.google-services")
|
||||||
id("dev.flutter.flutter-gradle-plugin")
|
id("dev.flutter.flutter-gradle-plugin")
|
||||||
}
|
}
|
||||||
|
|
||||||
val keystoreProperties = Properties()
|
|
||||||
val keystorePropertiesFile = rootProject.file("key.properties")
|
|
||||||
|
|
||||||
if (keystorePropertiesFile.exists()) {
|
|
||||||
FileInputStream(keystorePropertiesFile).use {
|
|
||||||
keystoreProperties.load(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
ndkVersion = flutter.ndkVersion
|
|
||||||
namespace = "com.yimaru.lms.app"
|
namespace = "com.yimaru.lms.app"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
applicationId = "com.yimaru.lms.app"
|
||||||
versionCode = flutter.versionCode
|
versionCode = flutter.versionCode
|
||||||
versionName = flutter.versionName
|
versionName = flutter.versionName
|
||||||
applicationId = "com.yimaru.lms.app"
|
|
||||||
targetSdk = flutter.targetSdkVersion
|
targetSdk = flutter.targetSdkVersion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
|
||||||
create("release") {
|
|
||||||
keyAlias = keystoreProperties["keyAlias"] as String
|
|
||||||
keyPassword = keystoreProperties["keyPassword"] as String
|
|
||||||
storePassword = keystoreProperties["storePassword"] as String
|
|
||||||
storeFile = keystoreProperties["storeFile"]?.let { file(it as String) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
getByName("release") {
|
release {
|
||||||
isMinifyEnabled = false
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
isShrinkResources = false
|
|
||||||
ndk { debugSymbolLevel = "FULL" }
|
|
||||||
signingConfig = signingConfigs.getByName("release")
|
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain(17)
|
|
||||||
|
|
||||||
compilerOptions {
|
|
||||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
|
||||||
implementation(platform("com.google.firebase:firebase-bom:34.10.0"))
|
|
||||||
}
|
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
source = "../.."
|
source = "../.."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,20 +14,20 @@
|
||||||
},
|
},
|
||||||
"oauth_client": [
|
"oauth_client": [
|
||||||
{
|
{
|
||||||
"client_id": "574860813475-3p3k63lkrfd113sn6jscgvdj0aigsg5s.apps.googleusercontent.com",
|
"client_id": "574860813475-01gh5tk0bu5bgj68r02sgh5pk5greoku.apps.googleusercontent.com",
|
||||||
|
"client_type": 1,
|
||||||
|
"android_info": {
|
||||||
|
"package_name": "com.yimaru.lms.app",
|
||||||
|
"certificate_hash": "fc91f52846d27c62bba3e16bc98982fb9953eca1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"client_id": "574860813475-631s3mo8ha2qc2jeb5e2aosn0967niik.apps.googleusercontent.com",
|
||||||
"client_type": 1,
|
"client_type": 1,
|
||||||
"android_info": {
|
"android_info": {
|
||||||
"package_name": "com.yimaru.lms.app",
|
"package_name": "com.yimaru.lms.app",
|
||||||
"certificate_hash": "928ead08b5e39d6a861a55ae7cceb8c402d1ee7a"
|
"certificate_hash": "928ead08b5e39d6a861a55ae7cceb8c402d1ee7a"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
"client_id": "574860813475-m90u87plqaac4tb8oug32k41usossiod.apps.googleusercontent.com",
|
|
||||||
"client_type": 1,
|
|
||||||
"android_info": {
|
|
||||||
"package_name": "com.yimaru.lms.app",
|
|
||||||
"certificate_hash": "29797902ad6a24212b9d9fad71562907956f6a6c"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"api_key": [
|
"api_key": [
|
||||||
|
|
|
||||||
41
android/app/proguard-rules.pro
vendored
|
|
@ -1,41 +0,0 @@
|
||||||
############################################
|
|
||||||
# Flutter
|
|
||||||
############################################
|
|
||||||
-keep class io.flutter.** { *; }
|
|
||||||
-keep class io.flutter.plugins.** { *; }
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Firebase (General Safe Rules)
|
|
||||||
############################################
|
|
||||||
-keep class com.google.firebase.** { *; }
|
|
||||||
-dontwarn com.google.firebase.**
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Google Sign-In
|
|
||||||
############################################
|
|
||||||
-keep class com.google.android.gms.auth.api.signin.** { *; }
|
|
||||||
-keep class com.google.android.gms.common.api.** { *; }
|
|
||||||
-dontwarn com.google.android.gms.**
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Play Services
|
|
||||||
############################################
|
|
||||||
-keep class com.google.android.gms.** { *; }
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# flutter_inappwebview
|
|
||||||
############################################
|
|
||||||
-keep class com.pichillilorenzo.flutter_inappwebview.** { *; }
|
|
||||||
-dontwarn com.pichillilorenzo.flutter_inappwebview.**
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Keep annotations
|
|
||||||
############################################
|
|
||||||
-keepattributes *Annotation*
|
|
||||||
|
|
||||||
|
|
||||||
############################################
|
|
||||||
# Google Play Core
|
|
||||||
############################################
|
|
||||||
-keep class com.google.android.play.core.** { *; }
|
|
||||||
-dontwarn com.google.android.play.core.**
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
|
||||||
<application
|
<application
|
||||||
android:label="Yimaru Academy"
|
android:label="Yimaru"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 69 B |
|
|
@ -1,9 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item>
|
<item android:drawable="?android:colorBackground" />
|
||||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
|
||||||
</item>
|
<!-- You can insert your own image assets here -->
|
||||||
<item>
|
<!-- <item>
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
<bitmap
|
||||||
</item>
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 69 B |
|
|
@ -1,9 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item>
|
<item android:drawable="@android:color/white" />
|
||||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
|
||||||
</item>
|
<!-- You can insert your own image assets here -->
|
||||||
<item>
|
<!-- <item>
|
||||||
<bitmap android:gravity="center" android:src="@drawable/splash"/>
|
<bitmap
|
||||||
</item>
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 544 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 721 B |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
|
@ -1,21 +0,0 @@
|
||||||
<?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">
|
|
||||||
<item name="android:forceDarkAllowed">false</item>
|
|
||||||
<item name="android:windowFullscreen">false</item>
|
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
|
||||||
<item name="android:windowSplashScreenBackground">#9E2891</item>
|
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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>
|
|
||||||
|
|
@ -5,10 +5,6 @@
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
<item name="android:forceDarkAllowed">false</item>
|
|
||||||
<item name="android:windowFullscreen">false</item>
|
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<?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">
|
|
||||||
<item name="android:forceDarkAllowed">false</item>
|
|
||||||
<item name="android:windowFullscreen">false</item>
|
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
|
||||||
<item name="android:windowSplashScreenBackground">#9E2891</item>
|
|
||||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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>
|
|
||||||
|
|
@ -5,10 +5,6 @@
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
<item name="android:forceDarkAllowed">false</item>
|
|
||||||
<item name="android:windowFullscreen">false</item>
|
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
|
||||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,2 @@
|
||||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.defaults.buildfeatures.resvalues=true
|
|
||||||
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
|
|
||||||
android.enableAppCompileTimeRClass=false
|
|
||||||
android.usesSdkInManifest.disallowed=false
|
|
||||||
android.uniquePackageNames=false
|
|
||||||
android.dependency.useConstraints=true
|
|
||||||
android.r8.strictFullModeForKeepRules=false
|
|
||||||
android.r8.optimizedResourceShrinking=false
|
|
||||||
android.builtInKotlin=false
|
|
||||||
android.newDsl=false
|
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,9 @@ pluginManagement {
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||||
id("com.android.application") version "9.1.0" apply false
|
id("com.android.application") version "8.13.2" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.3.0" apply false
|
id("org.jetbrains.kotlin.android") version "2.3.0" apply false
|
||||||
id("com.google.gms.google-services") version("4.4.4") apply false
|
id("com.google.gms.google-services") version("4.4.4") apply false
|
||||||
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 119 KiB |
|
|
@ -1,3 +0,0 @@
|
||||||
description: This file stores settings for Dart & Flutter DevTools.
|
|
||||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
|
||||||
extensions:
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"images" : [
|
|
||||||
{
|
|
||||||
"filename" : "background.png",
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "1x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "2x"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idiom" : "universal",
|
|
||||||
"scale" : "3x"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"author" : "xcode",
|
|
||||||
"version" : 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 69 B |
|
|
@ -1,23 +1,23 @@
|
||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "LaunchImage.png",
|
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "LaunchImage@2x.png",
|
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"filename" : "LaunchImage@3x.png",
|
|
||||||
"idiom" : "universal",
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"author" : "xcode",
|
"version" : 1,
|
||||||
"version" : 1
|
"author" : "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 68 B |
|
|
@ -16,19 +16,13 @@
|
||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
|
|
||||||
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
|
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
|
|
||||||
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
|
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
|
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
|
|
@ -38,7 +32,6 @@
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="1024" height="1024"/>
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
<image name="LaunchBackground" width="1" height="1"/>
|
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,49 @@
|
||||||
<?xml version="1.0" encoding="UTF-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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Yimaru App</string>
|
<string>Yimaru App</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>yimaru_app</string>
|
<string>yimaru_app</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
<string>Main</string>
|
<string>Main</string>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIStatusBarHidden</key>
|
</dict>
|
||||||
<false/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:yimaru_app/ui/views/profile/profile_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/profile_detail/profile_detail_view.dart';
|
import 'package:yimaru_app/ui/views/profile_detail/profile_detail_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart';
|
import 'package:yimaru_app/ui/views/downloads/downloads_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/progress/progress_view.dart';
|
import 'package:yimaru_app/ui/views/progress/progress_view.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/ongoing_progress/ongoing_progress_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart';
|
import 'package:yimaru_app/ui/views/account_privacy/account_privacy_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/support/support_view.dart';
|
import 'package:yimaru_app/ui/views/support/support_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/telegram_support/telegram_support_view.dart';
|
import 'package:yimaru_app/ui/views/telegram_support/telegram_support_view.dart';
|
||||||
|
|
@ -29,6 +30,7 @@ import 'package:yimaru_app/services/status_checker_service.dart';
|
||||||
import 'package:yimaru_app/ui/views/welcome/welcome_view.dart';
|
import 'package:yimaru_app/ui/views/welcome/welcome_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
|
import 'package:yimaru_app/ui/views/assessment/assessment_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart';
|
import 'package:yimaru_app/ui/views/learn_lesson/learn_lesson_view.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/failure/failure_view.dart';
|
||||||
import 'package:yimaru_app/services/permission_handler_service.dart';
|
import 'package:yimaru_app/services/permission_handler_service.dart';
|
||||||
import 'package:yimaru_app/services/image_picker_service.dart';
|
import 'package:yimaru_app/services/image_picker_service.dart';
|
||||||
import 'package:yimaru_app/services/google_auth_service.dart';
|
import 'package:yimaru_app/services/google_auth_service.dart';
|
||||||
|
|
@ -36,20 +38,6 @@ import 'package:yimaru_app/services/image_downloader_service.dart';
|
||||||
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart';
|
import 'package:yimaru_app/ui/views/forget_password/forget_password_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart';
|
import 'package:yimaru_app/ui/views/learn_lesson_detail/learn_lesson_detail_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart';
|
import 'package:yimaru_app/ui/views/learn_practice/learn_practice_view.dart';
|
||||||
import 'package:yimaru_app/ui/views/course_practice/course_practice_view.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/course_payment/course_payment_view.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/course_category/course_category_view.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/failure/failure_view.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/course_lesson/course_lesson_view.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/course_lesson_detail/course_lesson_detail_view.dart';
|
|
||||||
import 'package:yimaru_app/services/notification_service.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/duolingo/duolingo_view.dart';
|
|
||||||
import 'package:yimaru_app/services/smart_auth_service.dart';
|
|
||||||
import 'package:yimaru_app/services/course_service.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/course_subcategory/course_subcategory_view.dart';
|
|
||||||
import 'package:yimaru_app/ui/views/course/course_view.dart';
|
|
||||||
import 'package:yimaru_app/services/audio_player_service.dart';
|
|
||||||
import 'package:yimaru_app/services/voice_recorder_service.dart';
|
|
||||||
// @stacked-import
|
// @stacked-import
|
||||||
|
|
||||||
@StackedApp(
|
@StackedApp(
|
||||||
|
|
@ -61,6 +49,7 @@ import 'package:yimaru_app/services/voice_recorder_service.dart';
|
||||||
MaterialRoute(page: ProfileDetailView),
|
MaterialRoute(page: ProfileDetailView),
|
||||||
MaterialRoute(page: DownloadsView),
|
MaterialRoute(page: DownloadsView),
|
||||||
MaterialRoute(page: ProgressView),
|
MaterialRoute(page: ProgressView),
|
||||||
|
MaterialRoute(page: OngoingProgressView),
|
||||||
MaterialRoute(page: AccountPrivacyView),
|
MaterialRoute(page: AccountPrivacyView),
|
||||||
MaterialRoute(page: SupportView),
|
MaterialRoute(page: SupportView),
|
||||||
MaterialRoute(page: TelegramSupportView),
|
MaterialRoute(page: TelegramSupportView),
|
||||||
|
|
@ -76,18 +65,10 @@ import 'package:yimaru_app/services/voice_recorder_service.dart';
|
||||||
MaterialRoute(page: WelcomeView),
|
MaterialRoute(page: WelcomeView),
|
||||||
MaterialRoute(page: AssessmentView),
|
MaterialRoute(page: AssessmentView),
|
||||||
MaterialRoute(page: LearnLessonView),
|
MaterialRoute(page: LearnLessonView),
|
||||||
|
MaterialRoute(page: FailureView),
|
||||||
MaterialRoute(page: ForgetPasswordView),
|
MaterialRoute(page: ForgetPasswordView),
|
||||||
MaterialRoute(page: LearnLessonDetailView),
|
MaterialRoute(page: LearnLessonDetailView),
|
||||||
MaterialRoute(page: LearnPracticeView),
|
MaterialRoute(page: LearnPracticeView),
|
||||||
MaterialRoute(page: CoursePracticeView),
|
|
||||||
MaterialRoute(page: CoursePaymentView),
|
|
||||||
MaterialRoute(page: CourseCategoryView),
|
|
||||||
MaterialRoute(page: FailureView),
|
|
||||||
MaterialRoute(page: CourseLessonView),
|
|
||||||
MaterialRoute(page: CourseLessonDetailView),
|
|
||||||
MaterialRoute(page: DuolingoView),
|
|
||||||
MaterialRoute(page: CourseSubcategoryView),
|
|
||||||
MaterialRoute(page: CourseView),
|
|
||||||
// @stacked-route
|
// @stacked-route
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
|
@ -103,11 +84,6 @@ import 'package:yimaru_app/services/voice_recorder_service.dart';
|
||||||
LazySingleton(classType: ImagePickerService),
|
LazySingleton(classType: ImagePickerService),
|
||||||
LazySingleton(classType: GoogleAuthService),
|
LazySingleton(classType: GoogleAuthService),
|
||||||
LazySingleton(classType: ImageDownloaderService),
|
LazySingleton(classType: ImageDownloaderService),
|
||||||
LazySingleton(classType: NotificationService),
|
|
||||||
LazySingleton(classType: SmartAuthService),
|
|
||||||
LazySingleton(classType: CourseService),
|
|
||||||
LazySingleton(classType: AudioPlayerService),
|
|
||||||
LazySingleton(classType: VoiceRecorderService),
|
|
||||||
// @stacked-service
|
// @stacked-service
|
||||||
],
|
],
|
||||||
bottomsheets: [
|
bottomsheets: [
|
||||||
|
|
|
||||||
|
|
@ -12,19 +12,14 @@ import 'package:stacked_services/src/navigation/navigation_service.dart';
|
||||||
import 'package:stacked_shared/stacked_shared.dart';
|
import 'package:stacked_shared/stacked_shared.dart';
|
||||||
|
|
||||||
import '../services/api_service.dart';
|
import '../services/api_service.dart';
|
||||||
import '../services/audio_player_service.dart';
|
|
||||||
import '../services/authentication_service.dart';
|
import '../services/authentication_service.dart';
|
||||||
import '../services/course_service.dart';
|
|
||||||
import '../services/dio_service.dart';
|
import '../services/dio_service.dart';
|
||||||
import '../services/google_auth_service.dart';
|
import '../services/google_auth_service.dart';
|
||||||
import '../services/image_downloader_service.dart';
|
import '../services/image_downloader_service.dart';
|
||||||
import '../services/image_picker_service.dart';
|
import '../services/image_picker_service.dart';
|
||||||
import '../services/notification_service.dart';
|
|
||||||
import '../services/permission_handler_service.dart';
|
import '../services/permission_handler_service.dart';
|
||||||
import '../services/secure_storage_service.dart';
|
import '../services/secure_storage_service.dart';
|
||||||
import '../services/smart_auth_service.dart';
|
|
||||||
import '../services/status_checker_service.dart';
|
import '../services/status_checker_service.dart';
|
||||||
import '../services/voice_recorder_service.dart';
|
|
||||||
|
|
||||||
final locator = StackedLocator.instance;
|
final locator = StackedLocator.instance;
|
||||||
|
|
||||||
|
|
@ -49,9 +44,4 @@ Future<void> setupLocator({
|
||||||
locator.registerLazySingleton(() => ImagePickerService());
|
locator.registerLazySingleton(() => ImagePickerService());
|
||||||
locator.registerLazySingleton(() => GoogleAuthService());
|
locator.registerLazySingleton(() => GoogleAuthService());
|
||||||
locator.registerLazySingleton(() => ImageDownloaderService());
|
locator.registerLazySingleton(() => ImageDownloaderService());
|
||||||
locator.registerLazySingleton(() => NotificationService());
|
|
||||||
locator.registerLazySingleton(() => SmartAuthService());
|
|
||||||
locator.registerLazySingleton(() => CourseService());
|
|
||||||
locator.registerLazySingleton(() => AudioPlayerService());
|
|
||||||
locator.registerLazySingleton(() => VoiceRecorderService());
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ class DefaultFirebaseOptions {
|
||||||
projectId: 'yimaru-lms-e834e',
|
projectId: 'yimaru-lms-e834e',
|
||||||
storageBucket: 'yimaru-lms-e834e.firebasestorage.app',
|
storageBucket: 'yimaru-lms-e834e.firebasestorage.app',
|
||||||
androidClientId:
|
androidClientId:
|
||||||
'574860813475-3p3k63lkrfd113sn6jscgvdj0aigsg5s.apps.googleusercontent.com',
|
'574860813475-01gh5tk0bu5bgj68r02sgh5pk5greoku.apps.googleusercontent.com',
|
||||||
iosBundleId: 'com.yimaru.lms.app',
|
iosBundleId: 'com.yimaru.lms.app',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:toastification/toastification.dart';
|
import 'package:toastification/toastification.dart';
|
||||||
import 'package:yimaru_app/app/app.bottomsheets.dart';
|
import 'package:yimaru_app/app/app.bottomsheets.dart';
|
||||||
|
|
@ -6,15 +5,10 @@ import 'package:yimaru_app/app/app.dialogs.dart';
|
||||||
import 'package:yimaru_app/app/app.locator.dart';
|
import 'package:yimaru_app/app/app.locator.dart';
|
||||||
import 'package:yimaru_app/app/app.router.dart';
|
import 'package:yimaru_app/app/app.router.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:yimaru_app/services/notification_service.dart';
|
|
||||||
|
|
||||||
import 'firebase_options.dart';
|
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await setupLocator();
|
await setupLocator();
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
|
||||||
await locator<NotificationService>().initialize();
|
|
||||||
setupDialogUi();
|
setupDialogUi();
|
||||||
setupBottomSheetUi();
|
setupBottomSheetUi();
|
||||||
runApp(const MainApp());
|
runApp(const MainApp());
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ class Assessment {
|
||||||
|
|
||||||
final String? status;
|
final String? status;
|
||||||
|
|
||||||
final List<Option>? options;
|
|
||||||
|
|
||||||
@JsonKey(name: 'question_type')
|
@JsonKey(name: 'question_type')
|
||||||
final String? questionType;
|
final String? questionType;
|
||||||
|
|
||||||
|
|
@ -21,6 +19,8 @@ class Assessment {
|
||||||
@JsonKey(name: 'difficulty_level')
|
@JsonKey(name: 'difficulty_level')
|
||||||
final String? difficultyLevel;
|
final String? difficultyLevel;
|
||||||
|
|
||||||
|
final List<Option>? options;
|
||||||
|
|
||||||
const Assessment({
|
const Assessment({
|
||||||
this.id,
|
this.id,
|
||||||
this.points,
|
this.points,
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ Map<String, dynamic> _$AssessmentToJson(Assessment instance) =>
|
||||||
'id': instance.id,
|
'id': instance.id,
|
||||||
'points': instance.points,
|
'points': instance.points,
|
||||||
'status': instance.status,
|
'status': instance.status,
|
||||||
'options': instance.options,
|
|
||||||
'question_type': instance.questionType,
|
'question_type': instance.questionType,
|
||||||
'question_text': instance.questionText,
|
'question_text': instance.questionText,
|
||||||
'difficulty_level': instance.difficultyLevel,
|
'difficulty_level': instance.difficultyLevel,
|
||||||
|
'options': instance.options,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'course.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class Course {
|
|
||||||
final int? id;
|
|
||||||
|
|
||||||
final String? level;
|
|
||||||
|
|
||||||
final String? title;
|
|
||||||
|
|
||||||
final String? thumbnail;
|
|
||||||
|
|
||||||
final String? description;
|
|
||||||
|
|
||||||
@JsonKey(name: 'course_id')
|
|
||||||
final int? courseId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_active')
|
|
||||||
final bool? isActive;
|
|
||||||
|
|
||||||
@JsonKey(name: 'display_order')
|
|
||||||
final int? displayOrder;
|
|
||||||
|
|
||||||
const Course(
|
|
||||||
{this.id,
|
|
||||||
this.level,
|
|
||||||
this.title,
|
|
||||||
this.isActive,
|
|
||||||
this.courseId,
|
|
||||||
this.thumbnail,
|
|
||||||
this.description,
|
|
||||||
this.displayOrder});
|
|
||||||
|
|
||||||
factory Course.fromJson(Map<String, dynamic> json) => _$CourseFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'course.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
Course _$CourseFromJson(Map<String, dynamic> json) => Course(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
|
||||||
level: json['level'] as String?,
|
|
||||||
title: json['title'] as String?,
|
|
||||||
isActive: json['is_active'] as bool?,
|
|
||||||
courseId: (json['course_id'] as num?)?.toInt(),
|
|
||||||
thumbnail: json['thumbnail'] as String?,
|
|
||||||
description: json['description'] as String?,
|
|
||||||
displayOrder: (json['display_order'] as num?)?.toInt(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseToJson(Course instance) => <String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'level': instance.level,
|
|
||||||
'title': instance.title,
|
|
||||||
'thumbnail': instance.thumbnail,
|
|
||||||
'description': instance.description,
|
|
||||||
'course_id': instance.courseId,
|
|
||||||
'is_active': instance.isActive,
|
|
||||||
'display_order': instance.displayOrder,
|
|
||||||
};
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'course_category.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CourseCategory {
|
|
||||||
final int? id;
|
|
||||||
|
|
||||||
final String? name;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_active')
|
|
||||||
final bool? isActive;
|
|
||||||
|
|
||||||
const CourseCategory({this.id, this.name, this.isActive});
|
|
||||||
|
|
||||||
factory CourseCategory.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CourseCategoryFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseCategoryToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'course_category.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CourseCategory _$CourseCategoryFromJson(Map<String, dynamic> json) =>
|
|
||||||
CourseCategory(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
|
||||||
name: json['name'] as String?,
|
|
||||||
isActive: json['is_active'] as bool?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseCategoryToJson(CourseCategory instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'name': instance.name,
|
|
||||||
'is_active': instance.isActive,
|
|
||||||
};
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
import 'package:yimaru_app/models/course_progress.dart';
|
|
||||||
import 'package:yimaru_app/models/course.dart';
|
|
||||||
|
|
||||||
part 'course_detail.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CourseDetail {
|
|
||||||
final Course? course;
|
|
||||||
|
|
||||||
final CourseProgress? courseProgress;
|
|
||||||
|
|
||||||
const CourseDetail({this.course, this.courseProgress});
|
|
||||||
|
|
||||||
factory CourseDetail.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CourseDetailFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseDetailToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'course_detail.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CourseDetail _$CourseDetailFromJson(Map<String, dynamic> json) => CourseDetail(
|
|
||||||
course: json['course'] == null
|
|
||||||
? null
|
|
||||||
: Course.fromJson(json['course'] as Map<String, dynamic>),
|
|
||||||
courseProgress: json['courseProgress'] == null
|
|
||||||
? null
|
|
||||||
: CourseProgress.fromJson(
|
|
||||||
json['courseProgress'] as Map<String, dynamic>),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseDetailToJson(CourseDetail instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'course': instance.course,
|
|
||||||
'courseProgress': instance.courseProgress,
|
|
||||||
};
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'course_lesson.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CourseLesson {
|
|
||||||
int? id;
|
|
||||||
|
|
||||||
String? title;
|
|
||||||
|
|
||||||
int? duration;
|
|
||||||
|
|
||||||
String? status;
|
|
||||||
|
|
||||||
String? thumbnail;
|
|
||||||
|
|
||||||
String? resolution;
|
|
||||||
|
|
||||||
String? visibility;
|
|
||||||
|
|
||||||
String? description;
|
|
||||||
|
|
||||||
@JsonKey(name: 'video_url')
|
|
||||||
String? videoUrl;
|
|
||||||
|
|
||||||
@JsonKey(name: 'instructor_id')
|
|
||||||
int? instructorId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'sub_course_id')
|
|
||||||
int? courseId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'vimeo_status')
|
|
||||||
String? vimeoStatus;
|
|
||||||
|
|
||||||
@JsonKey(name: 'display_order')
|
|
||||||
int? displayOrder;
|
|
||||||
|
|
||||||
CourseLesson(
|
|
||||||
{this.id,
|
|
||||||
this.title,
|
|
||||||
this.status,
|
|
||||||
this.courseId,
|
|
||||||
this.videoUrl,
|
|
||||||
this.duration,
|
|
||||||
this.thumbnail,
|
|
||||||
this.visibility,
|
|
||||||
this.resolution,
|
|
||||||
this.vimeoStatus,
|
|
||||||
this.description,
|
|
||||||
this.displayOrder,
|
|
||||||
this.instructorId});
|
|
||||||
|
|
||||||
factory CourseLesson.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CourseLessonFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseLessonToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'course_lesson.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CourseLesson _$CourseLessonFromJson(Map<String, dynamic> json) => CourseLesson(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
|
||||||
title: json['title'] as String?,
|
|
||||||
status: json['status'] as String?,
|
|
||||||
courseId: (json['sub_course_id'] as num?)?.toInt(),
|
|
||||||
videoUrl: json['video_url'] as String?,
|
|
||||||
duration: (json['duration'] as num?)?.toInt(),
|
|
||||||
thumbnail: json['thumbnail'] as String?,
|
|
||||||
visibility: json['visibility'] as String?,
|
|
||||||
resolution: json['resolution'] as String?,
|
|
||||||
vimeoStatus: json['vimeo_status'] as String?,
|
|
||||||
description: json['description'] as String?,
|
|
||||||
displayOrder: (json['display_order'] as num?)?.toInt(),
|
|
||||||
instructorId: (json['instructor_id'] as num?)?.toInt(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseLessonToJson(CourseLesson instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'title': instance.title,
|
|
||||||
'duration': instance.duration,
|
|
||||||
'status': instance.status,
|
|
||||||
'thumbnail': instance.thumbnail,
|
|
||||||
'resolution': instance.resolution,
|
|
||||||
'visibility': instance.visibility,
|
|
||||||
'description': instance.description,
|
|
||||||
'video_url': instance.videoUrl,
|
|
||||||
'instructor_id': instance.instructorId,
|
|
||||||
'sub_course_id': instance.courseId,
|
|
||||||
'vimeo_status': instance.vimeoStatus,
|
|
||||||
'display_order': instance.displayOrder,
|
|
||||||
};
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'course_progress.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CourseProgress {
|
|
||||||
final String? level;
|
|
||||||
|
|
||||||
final String? title;
|
|
||||||
|
|
||||||
final String? description;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_locked')
|
|
||||||
final bool? isLocked;
|
|
||||||
|
|
||||||
@JsonKey(name: 'sub_course_id')
|
|
||||||
final int? courseId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'display_order')
|
|
||||||
final int? displayOrder;
|
|
||||||
|
|
||||||
@JsonKey(name: 'progress_status')
|
|
||||||
final String? progressStatus;
|
|
||||||
|
|
||||||
@JsonKey(name: 'progress_percentage')
|
|
||||||
final double? progressPercentage;
|
|
||||||
|
|
||||||
const CourseProgress(
|
|
||||||
{this.level,
|
|
||||||
this.title,
|
|
||||||
this.isLocked,
|
|
||||||
this.courseId,
|
|
||||||
this.description,
|
|
||||||
this.displayOrder,
|
|
||||||
this.progressStatus,
|
|
||||||
this.progressPercentage});
|
|
||||||
|
|
||||||
factory CourseProgress.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CourseProgressFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseProgressToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'course_progress.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CourseProgress _$CourseProgressFromJson(Map<String, dynamic> json) =>
|
|
||||||
CourseProgress(
|
|
||||||
level: json['level'] as String?,
|
|
||||||
title: json['title'] as String?,
|
|
||||||
isLocked: json['is_locked'] as bool?,
|
|
||||||
courseId: (json['sub_course_id'] as num?)?.toInt(),
|
|
||||||
description: json['description'] as String?,
|
|
||||||
displayOrder: (json['display_order'] as num?)?.toInt(),
|
|
||||||
progressStatus: json['progress_status'] as String?,
|
|
||||||
progressPercentage: (json['progress_percentage'] as num?)?.toDouble(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseProgressToJson(CourseProgress instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'level': instance.level,
|
|
||||||
'title': instance.title,
|
|
||||||
'description': instance.description,
|
|
||||||
'is_locked': instance.isLocked,
|
|
||||||
'sub_course_id': instance.courseId,
|
|
||||||
'display_order': instance.displayOrder,
|
|
||||||
'progress_status': instance.progressStatus,
|
|
||||||
'progress_percentage': instance.progressPercentage,
|
|
||||||
};
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'course_subcategory.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class CourseSubcategory {
|
|
||||||
final int? id;
|
|
||||||
|
|
||||||
final String? title;
|
|
||||||
|
|
||||||
final String? thumbnail;
|
|
||||||
|
|
||||||
final String? description;
|
|
||||||
|
|
||||||
@JsonKey(name: 'is_active')
|
|
||||||
final bool? isActive;
|
|
||||||
|
|
||||||
@JsonKey(name: 'category_id')
|
|
||||||
final int? categoryId;
|
|
||||||
|
|
||||||
const CourseSubcategory(
|
|
||||||
{this.id,
|
|
||||||
this.title,
|
|
||||||
this.isActive,
|
|
||||||
this.thumbnail,
|
|
||||||
this.categoryId,
|
|
||||||
this.description});
|
|
||||||
|
|
||||||
factory CourseSubcategory.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$CourseSubcategoryFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$CourseSubcategoryToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'course_subcategory.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
CourseSubcategory _$CourseSubcategoryFromJson(Map<String, dynamic> json) =>
|
|
||||||
CourseSubcategory(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
|
||||||
title: json['title'] as String?,
|
|
||||||
isActive: json['is_active'] as bool?,
|
|
||||||
thumbnail: json['thumbnail'] as String?,
|
|
||||||
categoryId: (json['category_id'] as num?)?.toInt(),
|
|
||||||
description: json['description'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$CourseSubcategoryToJson(CourseSubcategory instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'title': instance.title,
|
|
||||||
'thumbnail': instance.thumbnail,
|
|
||||||
'description': instance.description,
|
|
||||||
'is_active': instance.isActive,
|
|
||||||
'category_id': instance.categoryId,
|
|
||||||
};
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
part 'practice.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class Practice {
|
|
||||||
final int? id;
|
|
||||||
|
|
||||||
final String? title;
|
|
||||||
|
|
||||||
final String? status;
|
|
||||||
|
|
||||||
final String? persona;
|
|
||||||
|
|
||||||
final String? description;
|
|
||||||
|
|
||||||
@JsonKey(name: 'owner_id')
|
|
||||||
final int? ownerId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'set_type')
|
|
||||||
final String? setType;
|
|
||||||
|
|
||||||
@JsonKey(name: 'owner_type')
|
|
||||||
final String? ownerType;
|
|
||||||
|
|
||||||
@JsonKey(name: 'shuffle_questions')
|
|
||||||
final bool? shuffleQuestions;
|
|
||||||
|
|
||||||
const Practice(
|
|
||||||
{this.id,
|
|
||||||
this.title,
|
|
||||||
this.status,
|
|
||||||
this.setType,
|
|
||||||
this.persona,
|
|
||||||
this.ownerId,
|
|
||||||
this.ownerType,
|
|
||||||
this.description,
|
|
||||||
this.shuffleQuestions});
|
|
||||||
|
|
||||||
factory Practice.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$PracticeFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PracticeToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'practice.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
Practice _$PracticeFromJson(Map<String, dynamic> json) => Practice(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
|
||||||
title: json['title'] as String?,
|
|
||||||
status: json['status'] as String?,
|
|
||||||
setType: json['set_type'] as String?,
|
|
||||||
persona: json['persona'] as String?,
|
|
||||||
ownerId: (json['owner_id'] as num?)?.toInt(),
|
|
||||||
ownerType: json['owner_type'] as String?,
|
|
||||||
description: json['description'] as String?,
|
|
||||||
shuffleQuestions: json['shuffle_questions'] as bool?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$PracticeToJson(Practice instance) => <String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'title': instance.title,
|
|
||||||
'status': instance.status,
|
|
||||||
'persona': instance.persona,
|
|
||||||
'description': instance.description,
|
|
||||||
'owner_id': instance.ownerId,
|
|
||||||
'set_type': instance.setType,
|
|
||||||
'owner_type': instance.ownerType,
|
|
||||||
'shuffle_questions': instance.shuffleQuestions,
|
|
||||||
};
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
|
||||||
|
|
||||||
part 'practice_question.g.dart';
|
|
||||||
|
|
||||||
@JsonSerializable()
|
|
||||||
class PracticeQuestion {
|
|
||||||
final int? id;
|
|
||||||
|
|
||||||
final int? points;
|
|
||||||
|
|
||||||
final String? tips;
|
|
||||||
|
|
||||||
@JsonKey(name: 'set_id')
|
|
||||||
final int? setId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'question_id')
|
|
||||||
final int? questionId;
|
|
||||||
|
|
||||||
@JsonKey(name: 'display_order')
|
|
||||||
final int? displayOrder;
|
|
||||||
|
|
||||||
@JsonKey(name: 'question_text')
|
|
||||||
final String? questionText;
|
|
||||||
|
|
||||||
@JsonKey(name: 'question_type')
|
|
||||||
final String? questionType;
|
|
||||||
|
|
||||||
@JsonKey(name: 'question_status')
|
|
||||||
final String? questionStatus;
|
|
||||||
|
|
||||||
const PracticeQuestion(
|
|
||||||
{this.id,
|
|
||||||
this.tips,
|
|
||||||
this.setId,
|
|
||||||
this.points,
|
|
||||||
this.questionId,
|
|
||||||
this.questionText,
|
|
||||||
this.questionType,
|
|
||||||
this.displayOrder,
|
|
||||||
this.questionStatus});
|
|
||||||
|
|
||||||
factory PracticeQuestion.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$PracticeQuestionFromJson(json);
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PracticeQuestionToJson(this);
|
|
||||||
}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'practice_question.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
PracticeQuestion _$PracticeQuestionFromJson(Map<String, dynamic> json) =>
|
|
||||||
PracticeQuestion(
|
|
||||||
id: (json['id'] as num?)?.toInt(),
|
|
||||||
tips: json['tips'] as String?,
|
|
||||||
setId: (json['set_id'] as num?)?.toInt(),
|
|
||||||
points: (json['points'] as num?)?.toInt(),
|
|
||||||
questionId: (json['question_id'] as num?)?.toInt(),
|
|
||||||
questionText: json['question_text'] as String?,
|
|
||||||
questionType: json['question_type'] as String?,
|
|
||||||
displayOrder: (json['display_order'] as num?)?.toInt(),
|
|
||||||
questionStatus: json['question_status'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$PracticeQuestionToJson(PracticeQuestion instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'points': instance.points,
|
|
||||||
'tips': instance.tips,
|
|
||||||
'set_id': instance.setId,
|
|
||||||
'question_id': instance.questionId,
|
|
||||||
'display_order': instance.displayOrder,
|
|
||||||
'question_text': instance.questionText,
|
|
||||||
'question_type': instance.questionType,
|
|
||||||
'question_status': instance.questionStatus,
|
|
||||||
};
|
|
||||||
|
|
@ -12,10 +12,12 @@ class UserModel {
|
||||||
|
|
||||||
final String? country;
|
final String? country;
|
||||||
|
|
||||||
|
|
||||||
final String? occupation;
|
final String? occupation;
|
||||||
|
|
||||||
final bool? userInfoLoaded;
|
final bool? userInfoLoaded;
|
||||||
|
|
||||||
|
|
||||||
@JsonKey(name: 'user_id')
|
@JsonKey(name: 'user_id')
|
||||||
final int? userId;
|
final int? userId;
|
||||||
|
|
||||||
|
|
@ -53,41 +55,10 @@ class UserModel {
|
||||||
this.accessToken,
|
this.accessToken,
|
||||||
this.refreshToken,
|
this.refreshToken,
|
||||||
this.profilePicture,
|
this.profilePicture,
|
||||||
this.userInfoLoaded,
|
this.userInfoLoaded ,
|
||||||
this.profileCompleted,
|
this.profileCompleted,
|
||||||
});
|
});
|
||||||
|
|
||||||
UserModel copyWith(
|
|
||||||
{int? userId,
|
|
||||||
String? email,
|
|
||||||
String? gender,
|
|
||||||
String? region,
|
|
||||||
String? country,
|
|
||||||
String? lastName,
|
|
||||||
String? birthday,
|
|
||||||
String? firstName,
|
|
||||||
String? occupation,
|
|
||||||
String? accessToken,
|
|
||||||
String? refreshToken,
|
|
||||||
bool? userInfoLoaded,
|
|
||||||
bool? profileCompleted,
|
|
||||||
String? profilePicture}) =>
|
|
||||||
UserModel(
|
|
||||||
email: email ?? this.email,
|
|
||||||
userId: userId ?? this.userId,
|
|
||||||
gender: gender ?? this.gender,
|
|
||||||
region: region ?? this.region,
|
|
||||||
country: country ?? this.country,
|
|
||||||
lastName: lastName ?? this.lastName,
|
|
||||||
birthday: birthday ?? this.birthday,
|
|
||||||
firstName: firstName ?? this.firstName,
|
|
||||||
occupation: occupation ?? this.occupation,
|
|
||||||
accessToken: accessToken ?? this.accessToken,
|
|
||||||
refreshToken: refreshToken ?? this.refreshToken,
|
|
||||||
userInfoLoaded: userInfoLoaded ?? this.userInfoLoaded,
|
|
||||||
profilePicture: profilePicture ?? this.profilePicture,
|
|
||||||
profileCompleted: profileCompleted ?? this.profileCompleted);
|
|
||||||
|
|
||||||
factory UserModel.fromJson(Map<String, dynamic> json) =>
|
factory UserModel.fromJson(Map<String, dynamic> json) =>
|
||||||
_$UserModelFromJson(json);
|
_$UserModelFromJson(json);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
|
||||||
accessToken: json['access_token'] as String?,
|
accessToken: json['access_token'] as String?,
|
||||||
refreshToken: json['refresh_token'] as String?,
|
refreshToken: json['refresh_token'] as String?,
|
||||||
profilePicture: json['profile_picture_url'] as String?,
|
profilePicture: json['profile_picture_url'] as String?,
|
||||||
userInfoLoaded: json['userInfoLoaded'] as bool?,
|
|
||||||
profileCompleted: json['profile_completed'] as bool?,
|
profileCompleted: json['profile_completed'] as bool?,
|
||||||
|
userInfoLoaded: json['userInfoLoaded'] as bool? ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:yimaru_app/models/assessment.dart';
|
import 'package:yimaru_app/models/assessment.dart';
|
||||||
import 'package:yimaru_app/models/course_subcategory.dart';
|
|
||||||
import 'package:yimaru_app/models/course_category.dart';
|
|
||||||
import 'package:yimaru_app/models/course_lesson.dart';
|
|
||||||
import 'package:yimaru_app/models/course_progress.dart';
|
|
||||||
import 'package:yimaru_app/models/course.dart';
|
|
||||||
import 'package:yimaru_app/models/practice.dart';
|
|
||||||
import 'package:yimaru_app/models/practice_question.dart';
|
|
||||||
import 'package:yimaru_app/models/user_model.dart';
|
import 'package:yimaru_app/models/user_model.dart';
|
||||||
import 'package:yimaru_app/services/dio_service.dart';
|
import 'package:yimaru_app/services/dio_service.dart';
|
||||||
import 'package:yimaru_app/ui/common/app_constants.dart';
|
import 'package:yimaru_app/ui/common/app_constants.dart';
|
||||||
|
|
@ -15,15 +8,13 @@ import '../app/app.locator.dart';
|
||||||
import '../ui/common/enmus.dart';
|
import '../ui/common/enmus.dart';
|
||||||
|
|
||||||
class ApiService {
|
class ApiService {
|
||||||
// Dependency injection
|
|
||||||
final _service = locator<DioService>();
|
final _service = locator<DioService>();
|
||||||
|
|
||||||
// Register
|
// Register
|
||||||
Future<Map<String, dynamic>> registerWithEmail(
|
Future<Map<String, dynamic>> registerWithEmail(Map<String, dynamic> data) async {
|
||||||
Map<String, dynamic> data) async {
|
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
'$kBaseUrl/$kUserBaseUrl/$kRegisterUrl',
|
'$kBaseUrl/$kUserUrl/$kRegisterUrl',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -46,8 +37,8 @@ class ApiService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email login
|
// Email Login
|
||||||
Future<Map<String, dynamic>> login(Map<String, dynamic> data) async {
|
Future<Map<String, dynamic>> emailLogin(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
'$kBaseUrl/$kLoginUrl',
|
'$kBaseUrl/$kLoginUrl',
|
||||||
|
|
@ -106,7 +97,7 @@ class ApiService {
|
||||||
Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async {
|
Future<Map<String, dynamic>> verifyOtp(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
'$kBaseUrl/$kUserBaseUrl/$kVerifyOtpUrl',
|
'$kBaseUrl/$kUserUrl/$kVerifyOtpUrl',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
|
@ -133,7 +124,7 @@ class ApiService {
|
||||||
Future<Map<String, dynamic>> resendOtp(Map<String, dynamic> data) async {
|
Future<Map<String, dynamic>> resendOtp(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
'$kBaseUrl/$kUserBaseUrl/$kResendOtpUrl',
|
'$kBaseUrl/$kUserUrl/$kResendOtpUrl',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -161,7 +152,7 @@ class ApiService {
|
||||||
Map<String, dynamic> data) async {
|
Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
'$kBaseUrl/$kUserBaseUrl/$kRequestResetCode',
|
'$kBaseUrl/$kUserUrl/$kRequestResetCode',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -188,7 +179,7 @@ class ApiService {
|
||||||
Future<Map<String, dynamic>> resetPassword(Map<String, dynamic> data) async {
|
Future<Map<String, dynamic>> resetPassword(Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
'$kBaseUrl/$kUserBaseUrl/$kResetPassword',
|
'$kBaseUrl/$kUserUrl/$kResetPassword',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -215,7 +206,7 @@ class ApiService {
|
||||||
Future<Map<String, dynamic>> getProfileStatus(UserModel? user) async {
|
Future<Map<String, dynamic>> getProfileStatus(UserModel? user) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.get(
|
Response response = await _service.dio.get(
|
||||||
'$kBaseUrl/$kUserBaseUrl/${user?.userId}/$kProfileStatusUrl',
|
'$kBaseUrl/$kUserUrl/${user?.userId}/$kProfileStatusUrl',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
|
@ -242,7 +233,7 @@ class ApiService {
|
||||||
Future<Map<String, dynamic>> getProfileData(int? userId) async {
|
Future<Map<String, dynamic>> getProfileData(int? userId) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.get(
|
Response response = await _service.dio.get(
|
||||||
'$kBaseUrl/$kUserBaseUrl/$kGetUserUrl',
|
'$kBaseUrl/$kUserUrl/$kGetUserUrl/$userId',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
|
|
@ -270,7 +261,7 @@ class ApiService {
|
||||||
Map<String, dynamic> data) async {
|
Map<String, dynamic> data) async {
|
||||||
try {
|
try {
|
||||||
Response response = await _service.dio.put(
|
Response response = await _service.dio.put(
|
||||||
'$kBaseUrl/$kUserBaseUrl',
|
'$kBaseUrl/$kUserUrl',
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -300,7 +291,7 @@ class ApiService {
|
||||||
late FormData formData;
|
late FormData formData;
|
||||||
if (data['profile_picture_url']
|
if (data['profile_picture_url']
|
||||||
.toString()
|
.toString()
|
||||||
.contains('com.yimaru.lms.app/')) {
|
.contains('com.example.yimaru_app/')) {
|
||||||
formData = FormData.fromMap({
|
formData = FormData.fromMap({
|
||||||
'file': data['profile_picture_url'].toString().isNotEmpty
|
'file': data['profile_picture_url'].toString().isNotEmpty
|
||||||
? MultipartFile.fromFileSync(
|
? MultipartFile.fromFileSync(
|
||||||
|
|
@ -322,7 +313,7 @@ class ApiService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Response response = await _service.dio.post(
|
Response response = await _service.dio.post(
|
||||||
'$kBaseUrl/$kUserBaseUrl/$userId/$kUpdateProfileImage',
|
'$kBaseUrl/$kUserUrl/$userId/$kUpdateProfileImage',
|
||||||
data: formData,
|
data: formData,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -368,195 +359,4 @@ class ApiService {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Course categories
|
|
||||||
Future<List<CourseCategory>> getCourseCategories() async {
|
|
||||||
try {
|
|
||||||
List<CourseCategory> categories = [];
|
|
||||||
|
|
||||||
final Response response = await _service.dio
|
|
||||||
.get('$kBaseUrl/$kCourseBaseUrl/$kCourseCategoryUrl');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var data = response.data;
|
|
||||||
var decodedData = data['data']['categories'] as List;
|
|
||||||
categories = decodedData.map(
|
|
||||||
(e) {
|
|
||||||
return CourseCategory.fromJson(e);
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
return categories;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Course subcategory
|
|
||||||
Future<List<CourseSubcategory>> getCourseSubcategories(int id) async {
|
|
||||||
try {
|
|
||||||
List<CourseSubcategory> subcategories = [];
|
|
||||||
|
|
||||||
final Response response = await _service.dio.get(
|
|
||||||
'$kBaseUrl/$kCourseBaseUrl/$kCourseCategoryUrl/$id/$kCoursesUrl');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var data = response.data;
|
|
||||||
var decodedData = data['data']['courses'] as List;
|
|
||||||
subcategories = decodedData.map(
|
|
||||||
(e) {
|
|
||||||
return CourseSubcategory.fromJson(e);
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
return subcategories;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub-courses
|
|
||||||
Future<List<Course>> getCourses(int id) async {
|
|
||||||
try {
|
|
||||||
List<Course> courses = [];
|
|
||||||
|
|
||||||
final Response response = await _service.dio
|
|
||||||
.get('$kBaseUrl/$kCourseBaseUrl/$kCoursesUrl/$id/$kSubcoursesUrl');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var data = response.data;
|
|
||||||
var decodedData = data['data']['sub_courses'] as List;
|
|
||||||
courses = decodedData.map(
|
|
||||||
(e) {
|
|
||||||
return Course.fromJson(e);
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
return courses;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Course progress
|
|
||||||
Future<List<CourseProgress>> getCourseProgress(int id) async {
|
|
||||||
try {
|
|
||||||
List<CourseProgress> courseProgress = [];
|
|
||||||
|
|
||||||
final Response response =
|
|
||||||
await _service.dio.get('$kBaseUrl/$kCourseProgressUrl/$id');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var data = response.data;
|
|
||||||
var decodedData = data['data'] as List;
|
|
||||||
courseProgress = decodedData.map(
|
|
||||||
(e) {
|
|
||||||
return CourseProgress.fromJson(e);
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
return courseProgress;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Course videos
|
|
||||||
Future<List<CourseLesson>> getCourseLessons(int id) async {
|
|
||||||
try {
|
|
||||||
List<CourseLesson> courseLessons = [];
|
|
||||||
|
|
||||||
final Response response = await _service.dio.get(
|
|
||||||
'$kBaseUrl/$kCourseBaseUrl/$kSubcoursesUrl/$id/$kPublishedVideos');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var data = response.data;
|
|
||||||
var decodedData = data['data'] as List;
|
|
||||||
courseLessons = decodedData.map(
|
|
||||||
(e) {
|
|
||||||
return CourseLesson.fromJson(e);
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
return courseLessons;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete lesson
|
|
||||||
Future<Map<String, dynamic>> completeLesson(int id) async {
|
|
||||||
try {
|
|
||||||
Response response = await _service.dio.post(
|
|
||||||
'$kBaseUrl/$kLessonProgressUrl/$id/$kCompleteLessonUrl',
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
return {'status': ResponseStatus.success, 'message': 'Video completed'};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
'status': ResponseStatus.failure,
|
|
||||||
'message': 'Unknown Error Occurred'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} on DioException catch (e) {
|
|
||||||
return {
|
|
||||||
'status': ResponseStatus.failure,
|
|
||||||
'message': e.response?.data.toString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Course practices
|
|
||||||
Future<List<Practice>> getCoursePractices(Map<String, dynamic> data) async {
|
|
||||||
try {
|
|
||||||
List<Practice> coursePractices = [];
|
|
||||||
|
|
||||||
final Response response = await _service.dio
|
|
||||||
.get('$kBaseUrl/$kPracticeBaseUrl/$kCoursePractice', data: data);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var data = response.data;
|
|
||||||
var decodedData = data['data'] as List;
|
|
||||||
coursePractices = decodedData.map(
|
|
||||||
(e) {
|
|
||||||
return Practice.fromJson(e);
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
return coursePractices;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Course practic questions
|
|
||||||
Future<List<PracticeQuestion>> getCoursePracticeQuestions(int id) async {
|
|
||||||
try {
|
|
||||||
List<PracticeQuestion> coursePracticeQuestions = [];
|
|
||||||
|
|
||||||
final Response response = await _service.dio
|
|
||||||
.get('$kBaseUrl/$kPracticeBaseUrl/$id/$kCoursePracticeQuestions');
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
var data = response.data;
|
|
||||||
var decodedData = data['data'] as List;
|
|
||||||
coursePracticeQuestions = decodedData.map(
|
|
||||||
(e) {
|
|
||||||
return PracticeQuestion.fromJson(e);
|
|
||||||
},
|
|
||||||
).toList();
|
|
||||||
return coursePracticeQuestions;
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
} catch (e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
|
||||||
|
|
||||||
import '../ui/common/helper_functions.dart';
|
|
||||||
|
|
||||||
class AudioPlayerService with ListenableServiceMixin {
|
|
||||||
final AudioPlayer _player = AudioPlayer();
|
|
||||||
|
|
||||||
AudioPlayer get player => _player;
|
|
||||||
|
|
||||||
AudioPlayerService() {
|
|
||||||
_player.setReleaseMode(ReleaseMode.stop);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Streams
|
|
||||||
Stream<Duration> get positionStream => _player.onPositionChanged;
|
|
||||||
Stream<Duration> get durationStream => _player.onDurationChanged;
|
|
||||||
|
|
||||||
// Optional: player state
|
|
||||||
Stream<PlayerState> get stateStream => _player.onPlayerStateChanged;
|
|
||||||
|
|
||||||
Future<void> playUrl(String url) async {
|
|
||||||
final playableUrl = getPlayableUrl(url);
|
|
||||||
|
|
||||||
if (playableUrl == null) {
|
|
||||||
throw Exception("Invalid audio URL");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _player.play(UrlSource(playableUrl));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> playLocal(String url) async {
|
|
||||||
|
|
||||||
|
|
||||||
await _player.play(UrlSource(url));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> pause() async => await _player.pause();
|
|
||||||
|
|
||||||
Future<void> seek(Duration position) async => await _player.seek(position);
|
|
||||||
}
|
|
||||||
|
|
@ -4,20 +4,16 @@ import 'package:yimaru_app/models/user_model.dart';
|
||||||
import 'package:yimaru_app/services/secure_storage_service.dart';
|
import 'package:yimaru_app/services/secure_storage_service.dart';
|
||||||
|
|
||||||
class AuthenticationService with ListenableServiceMixin {
|
class AuthenticationService with ListenableServiceMixin {
|
||||||
// Dependency injection
|
|
||||||
final _secureService = locator<SecureStorageService>();
|
final _secureService = locator<SecureStorageService>();
|
||||||
|
|
||||||
// User data
|
|
||||||
UserModel? _user;
|
|
||||||
|
|
||||||
UserModel? get user => _user;
|
|
||||||
|
|
||||||
// Initialization
|
|
||||||
AuthenticationService() {
|
AuthenticationService() {
|
||||||
listenToReactiveValues([_user]);
|
listenToReactiveValues([_user]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check user logged in
|
UserModel? _user;
|
||||||
|
|
||||||
|
UserModel? get user => _user;
|
||||||
|
|
||||||
Future<bool> userLoggedIn() async {
|
Future<bool> userLoggedIn() async {
|
||||||
if (await _secureService.getString('userId') != null) {
|
if (await _secureService.getString('userId') != null) {
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -25,18 +21,14 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get access token
|
|
||||||
Future<String?> getAccessToken() async =>
|
Future<String?> getAccessToken() async =>
|
||||||
await _secureService.getString('accessToken');
|
await _secureService.getString('accessToken');
|
||||||
|
|
||||||
// Get refresh token
|
|
||||||
Future<String?> getRefreshToken() async =>
|
Future<String?> getRefreshToken() async =>
|
||||||
await _secureService.getString('refreshToken');
|
await _secureService.getString('refreshToken');
|
||||||
|
|
||||||
// Get user id
|
|
||||||
Future<int?> getUserId() async => await _secureService.getInt('userId');
|
Future<int?> getUserId() async => await _secureService.getInt('userId');
|
||||||
|
|
||||||
// Save tokens
|
|
||||||
Future<void> saveTokens({
|
Future<void> saveTokens({
|
||||||
required String access,
|
required String access,
|
||||||
required String refresh,
|
required String refresh,
|
||||||
|
|
@ -45,7 +37,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
await _secureService.setString('refreshToken', refresh);
|
await _secureService.setString('refreshToken', refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save user credential
|
|
||||||
Future<void> saveUserCredential(Map<String, dynamic> data) async {
|
Future<void> saveUserCredential(Map<String, dynamic> data) async {
|
||||||
await _secureService.setInt('userId', data['userId']);
|
await _secureService.setInt('userId', data['userId']);
|
||||||
await _secureService.setString('accessToken', data['accessToken']);
|
await _secureService.setString('accessToken', data['accessToken']);
|
||||||
|
|
@ -58,16 +50,10 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save profile status
|
|
||||||
Future<void> saveProfileStatus(bool value) async {
|
Future<void> saveProfileStatus(bool value) async {
|
||||||
await _secureService.setBool('profileCompleted', value);
|
await _secureService.setBool('profileCompleted', value);
|
||||||
|
|
||||||
_user = _user?.copyWith(
|
_user = UserModel(
|
||||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
|
||||||
profileCompleted: await _secureService.getBool('profileCompleted'),
|
|
||||||
);
|
|
||||||
|
|
||||||
/* UserModel(
|
|
||||||
email: _user?.email,
|
email: _user?.email,
|
||||||
gender: _user?.gender,
|
gender: _user?.gender,
|
||||||
region: _user?.region,
|
region: _user?.region,
|
||||||
|
|
@ -82,18 +68,12 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
profilePicture: _user?.profilePicture,
|
profilePicture: _user?.profilePicture,
|
||||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
||||||
profileCompleted: await _secureService.getBool('profileCompleted'));
|
profileCompleted: await _secureService.getBool('profileCompleted'));
|
||||||
*/
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveProfilePicture(String image) async {
|
Future<void> saveProfileImage(String image) async {
|
||||||
await _secureService.setString('profilePicture', image);
|
await _secureService.setString('profileImage', image);
|
||||||
_user = _user?.copyWith(
|
_user = UserModel(
|
||||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
|
||||||
profilePicture: await _secureService.getString('profilePicture'),
|
|
||||||
);
|
|
||||||
|
|
||||||
/*UserModel(
|
|
||||||
email: _user?.email,
|
email: _user?.email,
|
||||||
gender: _user?.gender,
|
gender: _user?.gender,
|
||||||
region: _user?.region,
|
region: _user?.region,
|
||||||
|
|
@ -107,16 +87,18 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
refreshToken: _user?.refreshToken,
|
refreshToken: _user?.refreshToken,
|
||||||
profileCompleted: _user?.profileCompleted,
|
profileCompleted: _user?.profileCompleted,
|
||||||
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
userInfoLoaded: _user?.userInfoLoaded ?? false,
|
||||||
profilePicture: await _secureService.getString('profilePicture'),
|
profilePicture: await _secureService.getString('profileImage'),
|
||||||
);
|
);
|
||||||
*/
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveUserData(UserModel data) async {
|
Future<void> saveUserData(
|
||||||
|
{required String image, required UserModel data}) async {
|
||||||
await _secureService.setBool('userInfoLoaded', true);
|
await _secureService.setBool('userInfoLoaded', true);
|
||||||
await _secureService.setBool(
|
await _secureService.setBool(
|
||||||
'profileCompleted', data.profileCompleted ?? false);
|
'profileCompleted', data.profileCompleted ?? false);
|
||||||
|
await _secureService.setString('profilePicture', image);
|
||||||
await _secureService.setString('email', data.email ?? '');
|
await _secureService.setString('email', data.email ?? '');
|
||||||
await _secureService.setString('region', data.region ?? '');
|
await _secureService.setString('region', data.region ?? '');
|
||||||
await _secureService.setString('gender', data.gender ?? '');
|
await _secureService.setString('gender', data.gender ?? '');
|
||||||
|
|
@ -131,6 +113,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
gender: data.gender,
|
gender: data.gender,
|
||||||
region: data.region,
|
region: data.region,
|
||||||
userInfoLoaded: true,
|
userInfoLoaded: true,
|
||||||
|
profilePicture: image,
|
||||||
userId: _user?.userId,
|
userId: _user?.userId,
|
||||||
country: data.country,
|
country: data.country,
|
||||||
lastName: data.lastName,
|
lastName: data.lastName,
|
||||||
|
|
@ -154,17 +137,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
await _secureService.setString('firstName', data['first_name']);
|
await _secureService.setString('firstName', data['first_name']);
|
||||||
await _secureService.setString('occupation', data['occupation']);
|
await _secureService.setString('occupation', data['occupation']);
|
||||||
|
|
||||||
_user = _user?.copyWith(
|
_user = UserModel(
|
||||||
region: await _secureService.getString('region'),
|
|
||||||
gender: await _secureService.getString('gender'),
|
|
||||||
country: await _secureService.getString('country'),
|
|
||||||
lastName: await _secureService.getString('lastName'),
|
|
||||||
birthday: await _secureService.getString('birthday'),
|
|
||||||
firstName: await _secureService.getString('firstName'),
|
|
||||||
occupation: await _secureService.getString('occupation'),
|
|
||||||
);
|
|
||||||
|
|
||||||
/*UserModel(
|
|
||||||
email: _user?.email,
|
email: _user?.email,
|
||||||
userId: _user?.userId,
|
userId: _user?.userId,
|
||||||
accessToken: _user?.accessToken,
|
accessToken: _user?.accessToken,
|
||||||
|
|
@ -178,7 +151,7 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
birthday: await _secureService.getString('birthday'),
|
birthday: await _secureService.getString('birthday'),
|
||||||
firstName: await _secureService.getString('firstName'),
|
firstName: await _secureService.getString('firstName'),
|
||||||
occupation: await _secureService.getString('occupation'),
|
occupation: await _secureService.getString('occupation'),
|
||||||
);*/
|
);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,14 +175,14 @@ class AuthenticationService with ListenableServiceMixin {
|
||||||
occupation: await _secureService.getString('occupation'),
|
occupation: await _secureService.getString('occupation'),
|
||||||
accessToken: await _secureService.getString('accessToken'),
|
accessToken: await _secureService.getString('accessToken'),
|
||||||
refreshToken: await _secureService.getString('refreshToken'),
|
refreshToken: await _secureService.getString('refreshToken'),
|
||||||
|
profilePicture: await _secureService.getString('profileImage'),
|
||||||
userInfoLoaded: await _secureService.getBool('userInfoLoaded'),
|
userInfoLoaded: await _secureService.getBool('userInfoLoaded'),
|
||||||
profilePicture: await _secureService.getString('profilePicture'),
|
|
||||||
profileCompleted: await _secureService.getBool('profileCompleted'),
|
profileCompleted: await _secureService.getBool('profileCompleted'),
|
||||||
);
|
);
|
||||||
return _user;
|
return _user;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logOut() async {
|
||||||
bool firstTimeInstall = await isFirstTimeInstall();
|
bool firstTimeInstall = await isFirstTimeInstall();
|
||||||
_user = null;
|
_user = null;
|
||||||
await _secureService.clear();
|
await _secureService.clear();
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
import 'package:yimaru_app/app/app.locator.dart';
|
|
||||||
import 'package:yimaru_app/models/course_progress.dart';
|
|
||||||
import 'package:yimaru_app/services/api_service.dart';
|
|
||||||
|
|
||||||
import '../models/course_detail.dart';
|
|
||||||
|
|
||||||
class CourseService {
|
|
||||||
final _apiService = locator<ApiService>();
|
|
||||||
|
|
||||||
Future<List<CourseDetail>> getCoursesDetail(int id) async {
|
|
||||||
final courses = await _apiService.getCourses(id);
|
|
||||||
final progress = await _apiService.getCourseProgress(id);
|
|
||||||
|
|
||||||
final progressMap = {
|
|
||||||
for (var p in progress.whereType<CourseProgress>()) p.courseId: p
|
|
||||||
};
|
|
||||||
|
|
||||||
return courses.map((course) {
|
|
||||||
return CourseDetail(
|
|
||||||
course: course,
|
|
||||||
courseProgress: progressMap[course.id],
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,21 +9,15 @@ import '../app/app.locator.dart';
|
||||||
import '../ui/common/app_constants.dart';
|
import '../ui/common/app_constants.dart';
|
||||||
|
|
||||||
class DioService {
|
class DioService {
|
||||||
// Dependency injection
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
final _authenticationService = locator<AuthenticationService>();
|
final _authenticationService = locator<AuthenticationService>();
|
||||||
|
|
||||||
// Initialization
|
|
||||||
final Dio _dio = Dio();
|
final Dio _dio = Dio();
|
||||||
|
|
||||||
Dio get dio => _dio;
|
|
||||||
|
|
||||||
final Dio _refreshDio = Dio(); // separate instance
|
final Dio _refreshDio = Dio(); // separate instance
|
||||||
|
|
||||||
bool _isRefreshing = false;
|
bool _isRefreshing = false;
|
||||||
final List<void Function()> _retryQueue = [];
|
final List<void Function()> _retryQueue = [];
|
||||||
|
|
||||||
// Initialization
|
|
||||||
DioService() {
|
DioService() {
|
||||||
_dio.options
|
_dio.options
|
||||||
..baseUrl = kBaseUrl
|
..baseUrl = kBaseUrl
|
||||||
|
|
@ -39,7 +33,6 @@ class DioService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response logger
|
|
||||||
void _onResponse(
|
void _onResponse(
|
||||||
Response response,
|
Response response,
|
||||||
ResponseInterceptorHandler handler,
|
ResponseInterceptorHandler handler,
|
||||||
|
|
@ -76,7 +69,6 @@ class DioService {
|
||||||
handler.next(options);
|
handler.next(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error logger
|
|
||||||
Future<void> _onError(
|
Future<void> _onError(
|
||||||
DioException error,
|
DioException error,
|
||||||
ErrorInterceptorHandler handler,
|
ErrorInterceptorHandler handler,
|
||||||
|
|
@ -133,7 +125,6 @@ class DioService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh token
|
|
||||||
Future<bool> _refreshToken() async {
|
Future<bool> _refreshToken() async {
|
||||||
final UserModel? user = await _authenticationService.getUser();
|
final UserModel? user = await _authenticationService.getUser();
|
||||||
|
|
||||||
|
|
@ -158,14 +149,15 @@ class DioService {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await _authenticationService.logout();
|
await _authenticationService.logOut();
|
||||||
await _navigationService.replaceWithLoginView();
|
await _navigationService.replaceWithLoginView();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check request if immediately after token refreshed
|
|
||||||
bool _isRefreshRequest(RequestOptions options) {
|
bool _isRefreshRequest(RequestOptions options) {
|
||||||
return options.path.contains(kRefreshTokenUrl);
|
return options.path.contains(kRefreshTokenUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Dio get dio => _dio;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,21 @@
|
||||||
import 'package:google_sign_in/google_sign_in.dart';
|
import 'package:google_sign_in/google_sign_in.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
|
||||||
import 'package:yimaru_app/ui/common/app_constants.dart';
|
import 'package:yimaru_app/ui/common/app_constants.dart';
|
||||||
|
|
||||||
class GoogleAuthService with ListenableServiceMixin {
|
class GoogleAuthService {
|
||||||
// Initialization
|
final GoogleSignIn signIn = GoogleSignIn.instance;
|
||||||
final GoogleSignIn _signIn = GoogleSignIn.instance;
|
|
||||||
|
|
||||||
GoogleSignInAccount? _googleUser;
|
Future<GoogleSignInAccount?> googleAuth() async {
|
||||||
|
|
||||||
GoogleSignInAccount? get googleUser => _googleUser;
|
|
||||||
|
|
||||||
// Initialization
|
|
||||||
GoogleAuthService() {
|
|
||||||
listenToReactiveValues([_googleUser]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Google logout
|
|
||||||
Future<void> logout() async {
|
|
||||||
await _signIn.signOut();
|
|
||||||
_googleUser = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Google authentication
|
|
||||||
Future<void> googleAuth() async {
|
|
||||||
try {
|
try {
|
||||||
await _signIn.initialize(serverClientId: kServerClientId).then((_) async {
|
GoogleSignInAccount? googleUser;
|
||||||
_googleUser = await _signIn.attemptLightweightAuthentication();
|
await signIn.initialize(serverClientId: kServerClientId).then((_) async {
|
||||||
|
googleUser = await signIn.attemptLightweightAuthentication();
|
||||||
|
|
||||||
_googleUser ??=
|
googleUser ??=
|
||||||
await _signIn.authenticate(scopeHint: ['email', 'profile']);
|
await signIn.authenticate(scopeHint: ['email', 'profile']);
|
||||||
});
|
});
|
||||||
notifyListeners();
|
return googleUser;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
rethrow;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,8 @@ import '../ui/common/app_constants.dart';
|
||||||
import 'dio_service.dart';
|
import 'dio_service.dart';
|
||||||
|
|
||||||
class ImageDownloaderService {
|
class ImageDownloaderService {
|
||||||
// Dependency injection
|
|
||||||
final _service = locator<DioService>();
|
final _service = locator<DioService>();
|
||||||
|
|
||||||
// Image downloader
|
|
||||||
Future<String> downloader(String? networkImage) async {
|
Future<String> downloader(String? networkImage) async {
|
||||||
late File image;
|
late File image;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,10 @@ import '../app/app.locator.dart';
|
||||||
import '../ui/common/ui_helpers.dart';
|
import '../ui/common/ui_helpers.dart';
|
||||||
|
|
||||||
class ImagePickerService {
|
class ImagePickerService {
|
||||||
// Dependency injection
|
|
||||||
final _permissionHandler = locator<PermissionHandlerService>();
|
final _permissionHandler = locator<PermissionHandlerService>();
|
||||||
|
|
||||||
// Initialization
|
|
||||||
final ImagePicker _picker = ImagePicker();
|
final ImagePicker _picker = ImagePicker();
|
||||||
|
|
||||||
// Pick image from gallery
|
|
||||||
Future<String?> gallery() async {
|
Future<String?> gallery() async {
|
||||||
try {
|
try {
|
||||||
PermissionStatus status =
|
PermissionStatus status =
|
||||||
|
|
@ -35,7 +32,6 @@ class ImagePickerService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick image from camera
|
|
||||||
Future<String?> camera() async {
|
Future<String?> camera() async {
|
||||||
try {
|
try {
|
||||||
PermissionStatus status =
|
PermissionStatus status =
|
||||||
|
|
|
||||||
|
|
@ -1,138 +0,0 @@
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
|
||||||
import 'package:yimaru_app/app/app.locator.dart';
|
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
|
||||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
||||||
await locator<NotificationService>().setupFlutterNotifications();
|
|
||||||
await locator<NotificationService>().showNotification(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
class NotificationService {
|
|
||||||
final _messaging = FirebaseMessaging.instance;
|
|
||||||
|
|
||||||
bool _isFlutterLocalNotificationInitialized = false;
|
|
||||||
|
|
||||||
final _localNotifications = FlutterLocalNotificationsPlugin();
|
|
||||||
|
|
||||||
Future<void> initialize() async {
|
|
||||||
// Initialize FCM token
|
|
||||||
await updateFCMToken();
|
|
||||||
|
|
||||||
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
|
||||||
|
|
||||||
// Request permission
|
|
||||||
await _requestPermission();
|
|
||||||
|
|
||||||
// setup message handle
|
|
||||||
await _setupMessageHandler();
|
|
||||||
|
|
||||||
// Subscribe to all devices
|
|
||||||
subscribeToTopic('yimaru');
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _requestPermission() async {
|
|
||||||
await _messaging.requestPermission(
|
|
||||||
alert: true,
|
|
||||||
badge: true,
|
|
||||||
sound: true,
|
|
||||||
carPlay: false,
|
|
||||||
provisional: false,
|
|
||||||
announcement: false,
|
|
||||||
criticalAlert: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setupFlutterNotifications() async {
|
|
||||||
if (_isFlutterLocalNotificationInitialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Android setup
|
|
||||||
const channel = AndroidNotificationChannel(
|
|
||||||
'yimaru', // id
|
|
||||||
'Yimaru', // title
|
|
||||||
importance: Importance.high,
|
|
||||||
);
|
|
||||||
|
|
||||||
await _localNotifications
|
|
||||||
.resolvePlatformSpecificImplementation<
|
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
|
||||||
?.createNotificationChannel(channel);
|
|
||||||
|
|
||||||
const initializationSettingsAndroid =
|
|
||||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
|
||||||
|
|
||||||
// IOS setup
|
|
||||||
const initializationSettingsDarwin = DarwinInitializationSettings();
|
|
||||||
|
|
||||||
const initializationSettings = InitializationSettings(
|
|
||||||
android: initializationSettingsAndroid,
|
|
||||||
iOS: initializationSettingsDarwin);
|
|
||||||
|
|
||||||
// Flutter notification setup
|
|
||||||
await _localNotifications.initialize(
|
|
||||||
settings: initializationSettings,
|
|
||||||
onDidReceiveNotificationResponse: (NotificationResponse response) {
|
|
||||||
if (response.payload == 'Page') {
|
|
||||||
// navigatorKey.currentState?.pushNamed('RouteName');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
_isFlutterLocalNotificationInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> showNotification(RemoteMessage message) async {
|
|
||||||
RemoteNotification? notification = message.notification;
|
|
||||||
AndroidNotification? android = message.notification?.android;
|
|
||||||
|
|
||||||
if (notification != null && android != null) {
|
|
||||||
await _localNotifications.show(
|
|
||||||
id: notification.hashCode,
|
|
||||||
title: notification.title,
|
|
||||||
body: notification.body,
|
|
||||||
notificationDetails: const NotificationDetails(
|
|
||||||
android: AndroidNotificationDetails('yimaru', 'Yimaru',
|
|
||||||
enableVibration: true,
|
|
||||||
priority: Priority.high,
|
|
||||||
icon: '@mipmap/ic_launcher',
|
|
||||||
importance: Importance.high),
|
|
||||||
iOS: DarwinNotificationDetails(
|
|
||||||
presentAlert: true, presentBadge: true, presentSound: true)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _setupMessageHandler() async {
|
|
||||||
// Foreground message
|
|
||||||
FirebaseMessaging.onMessage
|
|
||||||
.listen((RemoteMessage message) => showNotification(message));
|
|
||||||
|
|
||||||
// Background message
|
|
||||||
FirebaseMessaging.onMessageOpenedApp.listen(_handleBackgroundMessage);
|
|
||||||
|
|
||||||
// Opened app
|
|
||||||
final initialMessage = await _messaging.getInitialMessage();
|
|
||||||
|
|
||||||
if (initialMessage != null) {
|
|
||||||
_handleBackgroundMessage(initialMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleBackgroundMessage(RemoteMessage message) {
|
|
||||||
if (message.data['type'] == 'Page') {
|
|
||||||
// navigatorKey.currentState?.pushNamed('RouteName');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> subscribeToTopic(String topic) async {
|
|
||||||
await FirebaseMessaging.instance.subscribeToTopic(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateFCMToken() async {
|
|
||||||
// print('DEVICE TOKEN: ${await _messaging.getToken()}');
|
|
||||||
_messaging.onTokenRefresh.listen((newToken) {
|
|
||||||
// updateTokenOnServer(newToken);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'package:permission_handler/permission_handler.dart';
|
||||||
import '../ui/common/ui_helpers.dart';
|
import '../ui/common/ui_helpers.dart';
|
||||||
|
|
||||||
class PermissionHandlerService {
|
class PermissionHandlerService {
|
||||||
// Check permission category
|
|
||||||
Future<PermissionStatus> requestPermission(
|
Future<PermissionStatus> requestPermission(
|
||||||
Permission requestedPermission) async {
|
Permission requestedPermission) async {
|
||||||
if (requestedPermission == Permission.camera) {
|
if (requestedPermission == Permission.camera) {
|
||||||
|
|
@ -18,7 +17,6 @@ class PermissionHandlerService {
|
||||||
return PermissionStatus.denied;
|
return PermissionStatus.denied;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request permission
|
|
||||||
Future<PermissionStatus> request(Permission permission) async {
|
Future<PermissionStatus> request(Permission permission) async {
|
||||||
if (await permission.isDenied) {
|
if (await permission.isDenied) {
|
||||||
final PermissionStatus status = await permission.request();
|
final PermissionStatus status = await permission.request();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,8 @@ extension BoolParsing on String {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SecureStorageService {
|
class SecureStorageService {
|
||||||
// Initialization
|
// Create storage
|
||||||
|
|
||||||
late final FlutterSecureStorage _storage;
|
late final FlutterSecureStorage _storage;
|
||||||
|
|
||||||
SecureStorageService() {
|
SecureStorageService() {
|
||||||
|
|
@ -30,40 +31,33 @@ class SecureStorageService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear storage data
|
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
_storage.deleteAll();
|
_storage.deleteAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get boolean data from storage
|
|
||||||
Future<bool?> getBool(String key) async {
|
Future<bool?> getBool(String key) async {
|
||||||
String? result = await _storage.read(key: key);
|
String? result = await _storage.read(key: key);
|
||||||
return result?.parseBool();
|
return result?.parseBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get string data from storage
|
|
||||||
Future<String?> getString(String key) async {
|
Future<String?> getString(String key) async {
|
||||||
return await _storage.read(key: key);
|
return await _storage.read(key: key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get integer data from storage
|
|
||||||
Future<int?> getInt(String key) async {
|
Future<int?> getInt(String key) async {
|
||||||
return await _storage.read(key: key) == null
|
return await _storage.read(key: key) == null
|
||||||
? null
|
? null
|
||||||
: int.parse(await _storage.read(key: key) ?? '0');
|
: int.parse(await _storage.read(key: key) ?? '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save string data to storage
|
|
||||||
Future<void> setString(String key, String value) async {
|
Future<void> setString(String key, String value) async {
|
||||||
await _storage.write(key: key, value: value);
|
await _storage.write(key: key, value: value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save integer data to storage
|
|
||||||
Future<void> setInt(String key, int value) async {
|
Future<void> setInt(String key, int value) async {
|
||||||
await _storage.write(key: key, value: value.toString());
|
await _storage.write(key: key, value: value.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save boolean data to storage
|
|
||||||
Future<void> setBool(String key, bool value) async {
|
Future<void> setBool(String key, bool value) async {
|
||||||
await _storage.write(key: key, value: value.toString());
|
await _storage.write(key: key, value: value.toString());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import 'package:pinput/pinput.dart';
|
|
||||||
import 'package:smart_auth/smart_auth.dart';
|
|
||||||
|
|
||||||
class SmartAuthService implements SmsRetriever {
|
|
||||||
final SmartAuth _smartAuth = SmartAuth.instance;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> dispose() => _smartAuth.removeUserConsentApiListener();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<String?> getSmsCode() async {
|
|
||||||
final res = await _smartAuth.getSmsWithUserConsentApi();
|
|
||||||
if (res.hasData) {
|
|
||||||
final code = res.requireData.code;
|
|
||||||
|
|
||||||
return code;
|
|
||||||
} else if (res.isCanceled) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get listenForMultipleSms => true;
|
|
||||||
}
|
|
||||||
|
|
@ -8,28 +8,34 @@ import 'package:yimaru_app/services/secure_storage_service.dart';
|
||||||
import '../app/app.locator.dart';
|
import '../app/app.locator.dart';
|
||||||
|
|
||||||
class StatusCheckerService {
|
class StatusCheckerService {
|
||||||
// Dependency injection
|
|
||||||
final storage = locator<SecureStorageService>();
|
final storage = locator<SecureStorageService>();
|
||||||
|
|
||||||
// Initialization
|
|
||||||
bool _previousConnection = true;
|
bool _previousConnection = true;
|
||||||
|
|
||||||
bool get previousConnection => _previousConnection;
|
bool get previousConnection => _previousConnection;
|
||||||
|
|
||||||
// Get phone battery level
|
|
||||||
Future<int> getBatteryLevel() async {
|
Future<int> getBatteryLevel() async {
|
||||||
final battery = Battery();
|
final battery = Battery();
|
||||||
final batteryLevel = await battery.batteryLevel;
|
final batteryLevel = await battery.batteryLevel;
|
||||||
return batteryLevel;
|
return batteryLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check internet connection
|
Future<bool> userAuthenticated() async {
|
||||||
|
await checkAndUpdate();
|
||||||
|
|
||||||
|
if (await storage.getString('authenticated') != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> checkConnection() async {
|
Future<bool> checkConnection() async {
|
||||||
if (await InternetConnection().hasInternetAccess) {
|
if (await InternetConnection().hasInternetAccess) {
|
||||||
_previousConnection = true;
|
_previousConnection = true;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
if (_previousConnection) {
|
if (_previousConnection) {
|
||||||
|
// showErrorToast('Check your internet connection');
|
||||||
_previousConnection = false;
|
_previousConnection = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -37,7 +43,6 @@ class StatusCheckerService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check phone available storage
|
|
||||||
Future<int> getAvailableStorage() async {
|
Future<int> getAvailableStorage() async {
|
||||||
try {
|
try {
|
||||||
final availableStorage =
|
final availableStorage =
|
||||||
|
|
@ -48,7 +53,6 @@ class StatusCheckerService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for latest update
|
|
||||||
Future<void> checkAndUpdate() async {
|
Future<void> checkAndUpdate() async {
|
||||||
const requiredStorage = 500 * 1024 * 1024;
|
const requiredStorage = 500 * 1024 * 1024;
|
||||||
|
|
||||||
|
|
@ -58,12 +62,16 @@ class StatusCheckerService {
|
||||||
await getAvailableStorage(); // Implement getAvailableStorage
|
await getAvailableStorage(); // Implement getAvailableStorage
|
||||||
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
|
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
|
||||||
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
|
if (batteryLevel < 20 || storageAvailable < requiredStorage) {
|
||||||
|
// KewedeConst().showErrorToast(
|
||||||
// 'Unable to update app, please charge your phone & free up space.');
|
// 'Unable to update app, please charge your phone & free up space.');
|
||||||
} else if (batteryLevel < 20) {
|
} else if (batteryLevel < 20) {
|
||||||
|
// KewedeConst()
|
||||||
// .showErrorToast('Unable to update app, please charge your phone.');
|
// .showErrorToast('Unable to update app, please charge your phone.');
|
||||||
} else if (storageAvailable < requiredStorage) {
|
} else if (storageAvailable < requiredStorage) {
|
||||||
|
// KewedeConst()
|
||||||
// .showErrorToast('Unable to update app, please free up space.');
|
// .showErrorToast('Unable to update app, please free up space.');
|
||||||
}
|
}
|
||||||
|
// Show user-friendly message explaining why update failed and suggesting solutions (e.g., charge device, free up space)
|
||||||
return; // Prevent update from starting
|
return; // Prevent update from starting
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import 'package:stacked/stacked.dart';
|
|
||||||
import 'package:waveform_recorder/waveform_recorder.dart';
|
|
||||||
import 'package:yimaru_app/ui/common/enmus.dart';
|
|
||||||
|
|
||||||
class VoiceRecorderService with ListenableServiceMixin {
|
|
||||||
VoiceRecordingState _recordingState = VoiceRecordingState.pending;
|
|
||||||
|
|
||||||
VoiceRecordingState get recordingState => _recordingState;
|
|
||||||
|
|
||||||
final WaveformRecorderController _waveController =
|
|
||||||
WaveformRecorderController();
|
|
||||||
|
|
||||||
WaveformRecorderController get waveController => _waveController;
|
|
||||||
|
|
||||||
|
|
||||||
Future<void> startRecording() async {
|
|
||||||
|
|
||||||
await _waveController.startRecording();
|
|
||||||
_recordingState = VoiceRecordingState.recording;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> stopRecording() async {
|
|
||||||
await _waveController.stopRecording();
|
|
||||||
_recordingState = VoiceRecordingState.pending;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> getRecordedAudio() async {
|
|
||||||
final file = _waveController.file;
|
|
||||||
print('RECORDED $file');
|
|
||||||
if (file == null) return null;
|
|
||||||
return file.path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
const Color kcBlack = Colors.black;
|
const Color kcBlack = Colors.black;
|
||||||
const Color kcRed = Color(0xffFF4C4C);
|
const Color kcRed = Color(0xffFF4C4C);
|
||||||
const Color kcBlue = Color(0xff135BEC);
|
|
||||||
const Color kcGreen = Color(0xFF1DE964);
|
const Color kcGreen = Color(0xFF1DE964);
|
||||||
const Color kcBackgroundColor = kcWhite;
|
const Color kcBackgroundColor = kcWhite;
|
||||||
const Color kcWhite = Color(0xFFFFFFFF);
|
const Color kcWhite = Color(0xFFFFFFFF);
|
||||||
|
|
@ -11,7 +10,6 @@ const Color kcIndigo = Color(0xff6A1B9A);
|
||||||
const Color kcOrange = Color(0xFFF79400);
|
const Color kcOrange = Color(0xFFF79400);
|
||||||
const Color kcSkyBlue = Color(0xFF28B4CD);
|
const Color kcSkyBlue = Color(0xFF28B4CD);
|
||||||
const Color kcDarkGrey = Color(0xFF1A1B1E);
|
const Color kcDarkGrey = Color(0xFF1A1B1E);
|
||||||
const Color kcDeepGreen = Color(0xFF078E37);
|
|
||||||
const Color kcMediumGrey = Color(0xFF474A54);
|
const Color kcMediumGrey = Color(0xFF474A54);
|
||||||
const Color kcAquamarine = Color(0xFF1DE9B6);
|
const Color kcAquamarine = Color(0xFF1DE9B6);
|
||||||
const Color kcTransparent = Colors.transparent;
|
const Color kcTransparent = Colors.transparent;
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,34 @@
|
||||||
String kBaseUrl = 'https://api.yimaruacademy.com';
|
String kBaseUrl = 'http://195.35.29.82:8080';
|
||||||
//String baseUrl = 'https://api.yimaru.yaltopia.com';
|
//String baseUrl = 'https://api.yimaru.yaltopia.com';
|
||||||
|
|
||||||
String kCoursesUrl = 'courses';
|
String kGetUserUrl = 'single';
|
||||||
|
|
||||||
|
String kUserUrl = 'api/v1/user';
|
||||||
|
|
||||||
String kRegisterUrl = 'register';
|
String kRegisterUrl = 'register';
|
||||||
|
|
||||||
String kCoursePractice = 'by-owner';
|
|
||||||
|
|
||||||
String kUserBaseUrl = 'api/v1/user';
|
|
||||||
|
|
||||||
String kVerifyOtpUrl = 'verify-otp';
|
String kVerifyOtpUrl = 'verify-otp';
|
||||||
|
|
||||||
String kResendOtpUrl = 'resend-otp';
|
String kResendOtpUrl = 'resend-otp';
|
||||||
|
|
||||||
String kGetUserUrl = 'user-profile';
|
|
||||||
|
|
||||||
String kSubcoursesUrl = 'sub-courses';
|
|
||||||
|
|
||||||
String kCompleteLessonUrl = 'complete';
|
|
||||||
|
|
||||||
String kResetPassword = 'resetPassword';
|
String kResetPassword = 'resetPassword';
|
||||||
|
|
||||||
String kCourseCategoryUrl = 'categories';
|
|
||||||
|
|
||||||
String kRequestResetCode = 'sendResetCode';
|
String kRequestResetCode = 'sendResetCode';
|
||||||
|
|
||||||
String kPublishedVideos = 'videos/published';
|
|
||||||
|
|
||||||
String kCoursePracticeQuestions = 'questions';
|
|
||||||
|
|
||||||
String kUpdateProfileImage = 'profile-picture';
|
String kUpdateProfileImage = 'profile-picture';
|
||||||
|
|
||||||
String kRefreshTokenUrl = 'api/v1/auth/refresh';
|
String kRefreshTokenUrl = 'api/v1/auth/refresh';
|
||||||
|
|
||||||
String kLoginUrl = 'api/v1/auth/customer-login';
|
String kLoginUrl = 'api/v1/auth/customer-login';
|
||||||
|
|
||||||
String kPracticeBaseUrl = 'api/v1/question-sets';
|
|
||||||
|
|
||||||
String kProfileStatusUrl = 'is-profile-completed';
|
String kProfileStatusUrl = 'is-profile-completed';
|
||||||
|
|
||||||
String kCourseBaseUrl = 'api/v1/course-management';
|
|
||||||
|
|
||||||
String kLessonProgressUrl = 'api/v1/progress/videos';
|
|
||||||
|
|
||||||
String kGoogleAuthUrl = 'api/v1/auth/google/android';
|
String kGoogleAuthUrl = 'api/v1/auth/google/android';
|
||||||
|
|
||||||
String kCourseProgressUrl = 'api/v1/progress/courses';
|
|
||||||
|
|
||||||
String kAssessmentsUrl = 'api/v1/assessment/questions';
|
String kAssessmentsUrl = 'api/v1/assessment/questions';
|
||||||
|
|
||||||
String kEmptyImagePath = '/data/user/0/com.yimaru.lms.app/app_flutter';
|
|
||||||
|
|
||||||
String kSampleVideoUrl =
|
|
||||||
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
|
|
||||||
|
|
||||||
String kServerClientId =
|
String kServerClientId =
|
||||||
'574860813475-n5o17gpprdqmhcml99tiqhafb17rob0r.apps.googleusercontent.com';
|
'574860813475-n5o17gpprdqmhcml99tiqhafb17rob0r.apps.googleusercontent.com';
|
||||||
|
|
||||||
|
String kSampleVideoUrl =
|
||||||
|
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4';
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,10 @@
|
||||||
const String ksHomeBottomSheetTitle = 'Build Great Apps!';
|
|
||||||
|
|
||||||
const String ksSuggestion =
|
const String ksSuggestion =
|
||||||
"15 minutes a day can make you 3x more fluent in 3 month";
|
"15 minutes a day can make you 3x more fluent in 3 month";
|
||||||
|
const String ksHomeBottomSheetTitle = 'Build Great Apps!';
|
||||||
const String ksHomeBottomSheetDescription =
|
|
||||||
'Stacked is built to help you build better apps. Give us a chance and we\'ll prove it to you. Check out stacked.filledstacks.com to learn more';
|
|
||||||
|
|
||||||
const String ksPrivacyPolicy =
|
const String ksPrivacyPolicy =
|
||||||
'A brief, simple overview of Yimaru’s commitment to user privacy. Our goal is to be transparent about the data we collect and how we use it to enhance your learning experience.';
|
'A brief, simple overview of Yimaru’s commitment to user privacy. Our goal is to be transparent about the data we collect and how we use it to enhance your learning experience.';
|
||||||
|
const String ksHomeBottomSheetDescription =
|
||||||
const String ksCategorySubtitle =
|
'Stacked is built to help you build better apps. Give us a chance and we\'ll prove it to you. Check out stacked.filledstacks.com to learn more';
|
||||||
'Watch expert-led videos and reinforce your knowledge through guided practice activities.';
|
|
||||||
|
|
||||||
const String ksTerms = """
|
const String ksTerms = """
|
||||||
<p style="color:#9C2C91;font-size:13px;">
|
<p style="color:#9C2C91;font-size:13px;">
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,26 @@
|
||||||
// Login method
|
// Registration type
|
||||||
enum LoginMethod { phone, email, google }
|
enum RegistrationType { phone, email }
|
||||||
|
|
||||||
// Response status
|
// Report status
|
||||||
enum ResponseStatus { success, failure }
|
enum ResponseStatus { success, failure }
|
||||||
|
|
||||||
// Sign-up method
|
enum ProgressStatuses { pending, started, completed }
|
||||||
enum SignUpMethod { phone, email, google }
|
|
||||||
|
|
||||||
// Voice recording state
|
|
||||||
enum VoiceRecordingState { pending, recording }
|
|
||||||
|
|
||||||
// Levels
|
// Levels
|
||||||
enum ProficiencyLevels { a1, a2, b1, b2, none }
|
enum ProficiencyLevels { a1, a2, b1, b2, none }
|
||||||
|
|
||||||
// Progress status
|
|
||||||
enum ProgressStatuses { pending, started, completed }
|
|
||||||
|
|
||||||
// Duolingo assessment types
|
|
||||||
enum DuolingoAssessmentType { speaking, reading, writing, listening }
|
|
||||||
|
|
||||||
// State object
|
// State object
|
||||||
enum StateObjects {
|
enum StateObjects {
|
||||||
none,
|
|
||||||
courses,
|
|
||||||
homeView,
|
|
||||||
register,
|
|
||||||
verifyOtp,
|
verifyOtp,
|
||||||
resendOtp,
|
resendOtp,
|
||||||
profileImage,
|
profileImage,
|
||||||
courseLessons,
|
|
||||||
profileUpdate,
|
profileUpdate,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
subcategories,
|
|
||||||
loginWithEmail,
|
loginWithEmail,
|
||||||
coursePractice,
|
|
||||||
loginWithGoogle,
|
loginWithGoogle,
|
||||||
loadLessonVideo,
|
loadLessonVideo,
|
||||||
loadCourseVideo,
|
|
||||||
requestResetCode,
|
requestResetCode,
|
||||||
courseCategories,
|
registerWithEmail,
|
||||||
profileCompletion,
|
profileCompletion,
|
||||||
registerWithGoogle,
|
registerWithGoogle,
|
||||||
learnPracticeSample,
|
|
||||||
learnPracticeAnswer,
|
|
||||||
loginWithPhoneNumber,
|
|
||||||
learnPracticeQuestion,
|
|
||||||
recordLearnPracticeAnswer,
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,3 @@
|
||||||
// Split full name
|
|
||||||
import 'dart:math';
|
|
||||||
import 'dart:ui';
|
|
||||||
|
|
||||||
import 'app_colors.dart';
|
|
||||||
|
|
||||||
Map<String, String> splitFullName(String fullName) {
|
Map<String, String> splitFullName(String fullName) {
|
||||||
final parts = fullName.trim().split(RegExp(r'\s+'));
|
final parts = fullName.trim().split(RegExp(r'\s+'));
|
||||||
|
|
||||||
|
|
@ -21,50 +15,3 @@ Map<String, String> splitFullName(String fullName) {
|
||||||
'last_name': parts.sublist(1).join(' '),
|
'last_name': parts.sublist(1).join(' '),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Color getColor() {
|
|
||||||
final generator = Random();
|
|
||||||
int random = generator.nextInt(8);
|
|
||||||
if (random == 1) {
|
|
||||||
return kcRed.withValues(alpha: 0.2);
|
|
||||||
} else if (random == 2) {
|
|
||||||
return kcPrimaryColor.withValues(alpha: 0.2);
|
|
||||||
} else if (random == 3) {
|
|
||||||
return kcOrange.withValues(alpha: 0.2);
|
|
||||||
} else if (random == 4) {
|
|
||||||
return kcGreen.withValues(alpha: 0.2);
|
|
||||||
} else if (random == 5) {
|
|
||||||
return kcBlue.withValues(alpha: 0.2);
|
|
||||||
} else if (random == 6) {
|
|
||||||
return kcSkyBlue.withValues(alpha: 0.2);
|
|
||||||
} else if (random == 7) {
|
|
||||||
return kcIndigo.withValues(alpha: 0.2);
|
|
||||||
} else {
|
|
||||||
return kcAquamarine.withValues(alpha: 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? getPlayableUrl(String url) {
|
|
||||||
try {
|
|
||||||
// Case 1: /file/d/FILE_ID/view
|
|
||||||
final fileIdRegex = RegExp(r'/file/d/([a-zA-Z0-9_-]+)');
|
|
||||||
final match1 = fileIdRegex.firstMatch(url);
|
|
||||||
|
|
||||||
if (match1 != null) {
|
|
||||||
final fileId = match1.group(1);
|
|
||||||
return "https://drive.google.com/uc?export=download&id=$fileId";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case 2: open?id=FILE_ID
|
|
||||||
final uri = Uri.parse(url);
|
|
||||||
if (uri.queryParameters.containsKey('id')) {
|
|
||||||
final fileId = uri.queryParameters['id'];
|
|
||||||
return "https://drive.google.com/uc?export=download&id=$fileId";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Already converted or normal URL
|
|
||||||
return url;
|
|
||||||
} catch (e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:chewie/chewie.dart';
|
import 'package:chewie/chewie.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_html/flutter_html.dart';
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pinput/pinput.dart';
|
import 'package:pinput/pinput.dart';
|
||||||
import 'package:toastification/toastification.dart';
|
import 'package:toastification/toastification.dart';
|
||||||
import 'package:yimaru_app/ui/common/app_colors.dart';
|
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
|
|
@ -179,12 +178,6 @@ TextStyle style18P600 = const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle style16W600 = const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: kcWhite,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style18W600 = const TextStyle(
|
TextStyle style18W600 = const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: kcWhite,
|
color: kcWhite,
|
||||||
|
|
@ -197,11 +190,6 @@ TextStyle style25W600 = const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle style12RP600 = const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style12R700 = const TextStyle(
|
TextStyle style12R700 = const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
|
@ -209,24 +197,10 @@ TextStyle style12R700 = const TextStyle(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle style12P400 = const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style12DG400 = const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style14P400 = const TextStyle(
|
TextStyle style14P400 = const TextStyle(
|
||||||
color: kcPrimaryColor,
|
color: kcPrimaryColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle style14B400 = const TextStyle(
|
|
||||||
color: kcBlue,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style14P600 = const TextStyle(
|
TextStyle style14P600 = const TextStyle(
|
||||||
color: kcPrimaryColor,
|
color: kcPrimaryColor,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|
@ -244,29 +218,12 @@ TextStyle style25DG600 = const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle style16P600 = const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style16DG500 = const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style16DG600 = const TextStyle(
|
TextStyle style16DG600 = const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: kcDarkGrey,
|
color: kcDarkGrey,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle style16B600 = const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: kcBlue,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style18DG500 = const TextStyle(
|
TextStyle style18DG500 = const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
color: kcDarkGrey,
|
color: kcDarkGrey,
|
||||||
|
|
@ -279,24 +236,6 @@ TextStyle style18DG600 = const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
);
|
);
|
||||||
|
|
||||||
TextStyle style18G700 = const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
color: kcDeepGreen,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style18DG700 = const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style20DG700 = const TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
color: kcDarkGrey,
|
|
||||||
fontWeight: FontWeight.w700,
|
|
||||||
);
|
|
||||||
|
|
||||||
TextStyle style16DG400 = const TextStyle(
|
TextStyle style16DG400 = const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: kcDarkGrey,
|
color: kcDarkGrey,
|
||||||
|
|
@ -327,8 +266,6 @@ TextStyle validationStyle = const TextStyle(
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
);
|
);
|
||||||
|
|
||||||
Duration kDuration = const Duration(seconds: 1);
|
|
||||||
|
|
||||||
Style htmlDefaultStyle = Style(color: kcDarkGrey, fontSize: FontSize(16));
|
Style htmlDefaultStyle = Style(color: kcDarkGrey, fontSize: FontSize(16));
|
||||||
|
|
||||||
Map<String, Style> htmlStyle = {
|
Map<String, Style> htmlStyle = {
|
||||||
|
|
@ -359,19 +296,13 @@ ChewieProgressColors buildChewieProgressIndicator = ChewieProgressColors(
|
||||||
Widget buildToastDescription(String message) => Text(
|
Widget buildToastDescription(String message) => Text(
|
||||||
message,
|
message,
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
style: style14DG500,
|
style: const TextStyle(color: kcDarkGrey, fontWeight: FontWeight.w500),
|
||||||
);
|
|
||||||
|
|
||||||
Icon buildCloseIcon() => const Icon(
|
|
||||||
Icons.close,
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
void showErrorToast(String message) {
|
void showErrorToast(String message) {
|
||||||
toastification.show(
|
toastification.show(
|
||||||
showIcon: true,
|
showIcon: true,
|
||||||
dragToClose: true,
|
dragToClose: true,
|
||||||
icon: buildCloseIcon(),
|
|
||||||
showProgressBar: false,
|
showProgressBar: false,
|
||||||
applyBlurEffect: false,
|
applyBlurEffect: false,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
|
|
@ -382,21 +313,19 @@ void showErrorToast(String message) {
|
||||||
autoCloseDuration: const Duration(seconds: 3),
|
autoCloseDuration: const Duration(seconds: 3),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 15),
|
margin: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
borderSide: const BorderSide(color: kcPrimaryColor),
|
borderSide: const BorderSide(color: kcPrimaryColor),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: kcPrimaryColor,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Icon buildCheckIcon() => const Icon(
|
|
||||||
Icons.check,
|
|
||||||
color: kcPrimaryColor,
|
|
||||||
);
|
|
||||||
|
|
||||||
void showSuccessToast(String message) {
|
void showSuccessToast(String message) {
|
||||||
toastification.show(
|
toastification.show(
|
||||||
showIcon: true,
|
showIcon: true,
|
||||||
dragToClose: true,
|
dragToClose: true,
|
||||||
showProgressBar: false,
|
showProgressBar: false,
|
||||||
applyBlurEffect: false,
|
applyBlurEffect: false,
|
||||||
icon: buildCheckIcon(),
|
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
primaryColor: kcBackgroundColor,
|
primaryColor: kcBackgroundColor,
|
||||||
type: ToastificationType.success,
|
type: ToastificationType.success,
|
||||||
|
|
@ -405,5 +334,9 @@ void showSuccessToast(String message) {
|
||||||
autoCloseDuration: const Duration(seconds: 3),
|
autoCloseDuration: const Duration(seconds: 3),
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 15),
|
margin: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
borderSide: const BorderSide(color: kcPrimaryColor),
|
borderSide: const BorderSide(color: kcPrimaryColor),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.check,
|
||||||
|
color: kcPrimaryColor,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:email_validator/email_validator.dart';
|
import 'package:email_validator/email_validator.dart';
|
||||||
|
|
||||||
class FormValidator {
|
class FormValidator {
|
||||||
// Form validator
|
|
||||||
static String? validateForm(String? value) {
|
static String? validateForm(String? value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -10,58 +9,10 @@ class FormValidator {
|
||||||
if (value.isEmpty) {
|
if (value.isEmpty) {
|
||||||
return 'The field is required';
|
return 'The field is required';
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form validator
|
static String? validatePhoneNumber(String? value) {
|
||||||
static String? validateFullNameForm(String? value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isEmpty) {
|
|
||||||
return 'The field is required';
|
|
||||||
}
|
|
||||||
final regex = RegExp(r'^\S+\s+\S+.*$');
|
|
||||||
|
|
||||||
if (!regex.hasMatch(value.trim())) {
|
|
||||||
return "Enter your full name";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Email validator
|
|
||||||
static String? validateEmailForm(String? value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isEmpty) {
|
|
||||||
return 'The field is required';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!EmailValidator.validate(value)) {
|
|
||||||
return 'Invalid email format';
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password validator
|
|
||||||
static String? validatePasswordForm(String? value) {
|
|
||||||
if (value == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isEmpty) {
|
|
||||||
return 'The field is required';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phone number validator
|
|
||||||
static String? validatePhoneNumberForm(String? value) {
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -83,4 +34,31 @@ class FormValidator {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String? validateEmail(String? value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isEmpty) {
|
||||||
|
return 'The field is required';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EmailValidator.validate(value)) {
|
||||||
|
return 'Invalid email format';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? validatePassword(String? value) {
|
||||||
|
if (value == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.isEmpty) {
|
||||||
|
return 'The field is required';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
||||||
const AccountPrivacyView({Key? key}) : super(key: key);
|
const AccountPrivacyView({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AccountPrivacyViewModel viewModelBuilder(BuildContext context) =>
|
AccountPrivacyViewModel viewModelBuilder(
|
||||||
|
BuildContext context,
|
||||||
|
) =>
|
||||||
AccountPrivacyViewModel();
|
AccountPrivacyViewModel();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -55,9 +57,8 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildAppbar(AccountPrivacyViewModel viewModel) => SmallAppBar(
|
Widget _buildAppbar(AccountPrivacyViewModel viewModel) => SmallAppBar(
|
||||||
showBackButton: true,
|
|
||||||
onTap: viewModel.pop,
|
|
||||||
title: 'Account Privacy',
|
title: 'Account Privacy',
|
||||||
|
onTap: viewModel.pop,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildContentWrapper(AccountPrivacyViewModel viewModel) =>
|
Widget _buildContentWrapper(AccountPrivacyViewModel viewModel) =>
|
||||||
|
|
@ -106,7 +107,7 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
||||||
|
|
||||||
Widget _buildHeader(String title) => Text(
|
Widget _buildHeader(String title) => Text(
|
||||||
title,
|
title,
|
||||||
style: style18DG700,
|
style: style18DG600,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _buildLanguageMenu(AccountPrivacyViewModel viewModel) =>
|
Widget _buildLanguageMenu(AccountPrivacyViewModel viewModel) =>
|
||||||
|
|
@ -145,8 +146,8 @@ class AccountPrivacyView extends StackedView<AccountPrivacyViewModel> {
|
||||||
);
|
);
|
||||||
Widget _buildDeleteButton() => CustomElevatedButton(
|
Widget _buildDeleteButton() => CustomElevatedButton(
|
||||||
height: 55,
|
height: 55,
|
||||||
borderRadius: 12,
|
|
||||||
text: 'Delete Account',
|
text: 'Delete Account',
|
||||||
|
borderRadius: 12,
|
||||||
foregroundColor: kcRed,
|
foregroundColor: kcRed,
|
||||||
backgroundColor: kcRed.withOpacity(0.25),
|
backgroundColor: kcRed.withOpacity(0.25),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import 'package:yimaru_app/app/app.router.dart';
|
||||||
import '../../../app/app.locator.dart';
|
import '../../../app/app.locator.dart';
|
||||||
|
|
||||||
class AccountPrivacyViewModel extends BaseViewModel {
|
class AccountPrivacyViewModel extends BaseViewModel {
|
||||||
// Dependency injection
|
|
||||||
final _navigationService = locator<NavigationService>();
|
final _navigationService = locator<NavigationService>();
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/screens/Assessment_form_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/Assessment_form_screen.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/assessment/screens/assessment_completion_screen.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/assessment/screens/assessment_failure_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/screens/assessment_intro_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/assessment_intro_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/screens/assessment_result_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/assessment_result_screen.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/assessment/screens/result_analysis_screen.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/assessment/screens/retake_assessment_screen.dart';
|
||||||
import 'package:yimaru_app/ui/views/assessment/screens/start_lesson_screen.dart';
|
import 'package:yimaru_app/ui/views/assessment/screens/start_lesson_screen.dart';
|
||||||
|
|
||||||
import 'assessment_viewmodel.dart';
|
import 'assessment_viewmodel.dart';
|
||||||
|
|
@ -19,23 +23,13 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
|
||||||
super.onViewModelReady(viewModel);
|
super.onViewModelReady(viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
AssessmentViewModel viewModelBuilder(BuildContext context) =>
|
|
||||||
AssessmentViewModel();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget builder(
|
Widget builder(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AssessmentViewModel viewModel,
|
AssessmentViewModel viewModel,
|
||||||
Widget? child,
|
Widget? child,
|
||||||
) =>
|
) =>
|
||||||
_buildAssessmentScreensWrapper(viewModel);
|
_buildAssessmentScreens(viewModel);
|
||||||
|
|
||||||
Widget _buildAssessmentScreensWrapper(AssessmentViewModel viewModel) =>
|
|
||||||
PopScope(
|
|
||||||
canPop: viewModel.currentPage == 0 ? true : false,
|
|
||||||
onPopInvokedWithResult: (value, data) => viewModel.goBack(),
|
|
||||||
child: _buildAssessmentScreens(viewModel));
|
|
||||||
|
|
||||||
Widget _buildAssessmentScreens(AssessmentViewModel viewModel) => IndexedStack(
|
Widget _buildAssessmentScreens(AssessmentViewModel viewModel) => IndexedStack(
|
||||||
index: viewModel.currentPage,
|
index: viewModel.currentPage,
|
||||||
|
|
@ -59,7 +53,21 @@ class AssessmentView extends StackedView<AssessmentViewModel> {
|
||||||
|
|
||||||
Widget _buildAssessment() => const AssessmentFormScreen();
|
Widget _buildAssessment() => const AssessmentFormScreen();
|
||||||
|
|
||||||
|
Widget _buildAssessmentFailure() => const AssessmentFailureScreen();
|
||||||
|
|
||||||
|
Widget _buildRetakeAssessment() => const RetakeAssessmentScreen();
|
||||||
|
|
||||||
|
Widget _buildResultAnalysis() => const ResultAnalysisScreen();
|
||||||
|
|
||||||
|
Widget _buildAssessmentCompletion() => const AssessmentCompletionScreen();
|
||||||
|
|
||||||
Widget _buildAssessmentResult() => const AssessmentResultScreen();
|
Widget _buildAssessmentResult() => const AssessmentResultScreen();
|
||||||
|
|
||||||
Widget _buildStartLesson() => const StartLessonScreen();
|
Widget _buildStartLesson() => const StartLessonScreen();
|
||||||
|
|
||||||
|
@override
|
||||||
|
AssessmentViewModel viewModelBuilder(
|
||||||
|
BuildContext context,
|
||||||
|
) =>
|
||||||
|
AssessmentViewModel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
@ -11,9 +13,9 @@ import '../../../models/assessment.dart';
|
||||||
import '../../../services/api_service.dart';
|
import '../../../services/api_service.dart';
|
||||||
import '../../common/app_colors.dart';
|
import '../../common/app_colors.dart';
|
||||||
import '../../common/ui_helpers.dart';
|
import '../../common/ui_helpers.dart';
|
||||||
|
import '../home/home_view.dart';
|
||||||
|
|
||||||
class AssessmentViewModel extends BaseViewModel {
|
class AssessmentViewModel extends BaseViewModel {
|
||||||
// Dependency injection
|
|
||||||
final _apiService = locator<ApiService>();
|
final _apiService = locator<ApiService>();
|
||||||
final _dialogService = locator<DialogService>();
|
final _dialogService = locator<DialogService>();
|
||||||
final _statusChecker = locator<StatusCheckerService>();
|
final _statusChecker = locator<StatusCheckerService>();
|
||||||
|
|
@ -205,7 +207,6 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-app navigation
|
|
||||||
void next({int? page}) async {
|
void next({int? page}) async {
|
||||||
if (page == null) {
|
if (page == null) {
|
||||||
if (_previousPage != 0) {
|
if (_previousPage != 0) {
|
||||||
|
|
@ -220,37 +221,28 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
void goBack() {
|
void pop() {
|
||||||
if (_currentPage == 0) {
|
if (_currentPage == 0 || _currentPage == 3 /*7*/) {
|
||||||
_navigationService.back();
|
_navigationService.back();
|
||||||
} else if (_currentPage == 2) {
|
} else if (_currentPage != 0 && _currentPage != 3) {
|
||||||
_currentPage = 0;
|
_currentPage--;
|
||||||
rebuildUi();
|
|
||||||
} else if (_currentPage == 3) {
|
|
||||||
if (_proficiencyLevel != ProficiencyLevels.none) {
|
|
||||||
_currentPage--;
|
|
||||||
} else {
|
|
||||||
_currentPage = 0;
|
|
||||||
}
|
|
||||||
rebuildUi();
|
rebuildUi();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
void pop() => _navigationService.back();
|
|
||||||
|
|
||||||
Future<void> navigateToLanguage() async =>
|
Future<void> navigateToLanguage() async =>
|
||||||
await _navigationService.navigateToLanguageView();
|
await _navigationService.navigateToLanguageView();
|
||||||
|
|
||||||
Future<void> replaceWithHome() async =>
|
Future<void> replaceWithHome() async =>
|
||||||
await _navigationService.clearStackAndShow(Routes.homeView);
|
await _navigationService.clearStackAndShowView(const HomeView());
|
||||||
|
|
||||||
// Remote api call
|
// Remote api call
|
||||||
Future<void> getAssessments() async => await runBusyFuture(_getAssessments());
|
Future<void> getAssessments() async => await runBusyFuture(_getAssessments());
|
||||||
|
|
||||||
Future<void> _getAssessments() async {
|
Future<void> _getAssessments() async {
|
||||||
if (await _statusChecker.checkConnection()) {
|
if (await _statusChecker.checkConnection()) {
|
||||||
_assessments = await _apiService.getAssessments();
|
List<Assessment> response = await _apiService.getAssessments();
|
||||||
/*
|
/*
|
||||||
for (int i = 0; i < 6; i++) {
|
for (int i = 0; i < 6; i++) {
|
||||||
final generator = Random();
|
final generator = Random();
|
||||||
|
|
@ -258,6 +250,7 @@ class AssessmentViewModel extends BaseViewModel {
|
||||||
response.add(response[random]);
|
response.add(response[random]);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
_assessments = response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
|
import 'package:yimaru_app/ui/views/assessment/assessment_viewmodel.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
||||||
|
|
||||||
|
class AssessmentCompletionScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
|
const AssessmentCompletionScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
|
||||||
|
_buildScaffoldWrapper(viewModel);
|
||||||
|
|
||||||
|
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
body: _buildScaffold(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildScaffoldChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
|
||||||
|
[_buildAppBar(), _buildExpandedBody(viewModel)];
|
||||||
|
|
||||||
|
Widget _buildAppBar() => const LargeAppBar(
|
||||||
|
showBackButton: false,
|
||||||
|
showLanguageSelection: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
|
||||||
|
Expanded(child: _buildBodyWrapper(viewModel));
|
||||||
|
|
||||||
|
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildBody(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildBody(AssessmentViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: _buildBodyChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
|
||||||
|
[_buildUpperColumn(viewModel), _buildContinueButtonWrapper(viewModel)];
|
||||||
|
|
||||||
|
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: _buildUpperColumnChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
|
||||||
|
verticalSpaceLarge,
|
||||||
|
_buildIcon(),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildTitle(),
|
||||||
|
verticalSpaceSmall,
|
||||||
|
_buildSubtitle(),
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildIcon() => SvgPicture.asset(
|
||||||
|
'assets/icons/complete.svg',
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildTitle() => Text(
|
||||||
|
'Assessment complete!',
|
||||||
|
style: style25DG600,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSubtitle() => Text(
|
||||||
|
'We’re now analyzing your speaking skills',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: style14MG400,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContinueButtonWrapper(AssessmentViewModel viewModel) => Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 50),
|
||||||
|
child: _buildContinueButton(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
borderRadius: 12,
|
||||||
|
text: 'View My Results',
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
onTap: () => viewModel.next(),
|
||||||
|
backgroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
121
lib/ui/views/assessment/screens/assessment_failure_screen.dart
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/svg.dart';
|
||||||
|
import 'package:stacked/stacked.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/app_colors.dart';
|
||||||
|
import 'package:yimaru_app/ui/common/ui_helpers.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/custom_elevated_button.dart';
|
||||||
|
import 'package:yimaru_app/ui/widgets/large_app_bar.dart';
|
||||||
|
|
||||||
|
import '../assessment_viewmodel.dart';
|
||||||
|
|
||||||
|
class AssessmentFailureScreen extends ViewModelWidget<AssessmentViewModel> {
|
||||||
|
const AssessmentFailureScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, AssessmentViewModel viewModel) =>
|
||||||
|
_buildScaffoldWrapper(viewModel);
|
||||||
|
|
||||||
|
Widget _buildScaffoldWrapper(AssessmentViewModel viewModel) => Scaffold(
|
||||||
|
backgroundColor: kcBackgroundColor,
|
||||||
|
body: _buildScaffold(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildScaffold(AssessmentViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: _buildScaffoldChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildScaffoldChildren(AssessmentViewModel viewModel) =>
|
||||||
|
[_buildAppBar(viewModel), _buildExpandedBody(viewModel)];
|
||||||
|
|
||||||
|
Widget _buildAppBar(AssessmentViewModel viewModel) => LargeAppBar(
|
||||||
|
showBackButton: false,
|
||||||
|
showLanguageSelection: true,
|
||||||
|
onLanguage: () async => await viewModel.navigateToLanguage(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildExpandedBody(AssessmentViewModel viewModel) =>
|
||||||
|
Expanded(child: _buildBodyWrapper(viewModel));
|
||||||
|
|
||||||
|
Widget _buildBodyWrapper(AssessmentViewModel viewModel) => Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 15),
|
||||||
|
child: _buildBody(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildBody(AssessmentViewModel viewModel) => Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: _buildBodyChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildBodyChildren(AssessmentViewModel viewModel) =>
|
||||||
|
[_buildUpperColumn(viewModel), _buildLowerColumn(viewModel)];
|
||||||
|
|
||||||
|
Widget _buildUpperColumn(AssessmentViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: _buildUpperColumnChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildUpperColumnChildren(AssessmentViewModel viewModel) => [
|
||||||
|
verticalSpaceLarge,
|
||||||
|
_buildIcon(),
|
||||||
|
verticalSpaceMedium,
|
||||||
|
_buildTitle(),
|
||||||
|
verticalSpaceSmall,
|
||||||
|
_buildSubtitle(),
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildIcon() => SvgPicture.asset('assets/icons/alert.svg');
|
||||||
|
|
||||||
|
Widget _buildTitle() => Text(
|
||||||
|
'We didn’t get enough from your assessment',
|
||||||
|
style: style25DG600,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSubtitle() => Text(
|
||||||
|
'Your assessment wasn’t long enough for us to analyze your speaking level. You can retake the call to get accurate results ',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: style14MG400,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildLowerColumn(AssessmentViewModel viewModel) => Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: _buildLowerColumnChildren(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
List<Widget> _buildLowerColumnChildren(AssessmentViewModel viewModel) => [
|
||||||
|
_buildContinueButton(viewModel),
|
||||||
|
verticalSpaceSmall,
|
||||||
|
_buildSkipButtonWrapper(viewModel)
|
||||||
|
];
|
||||||
|
|
||||||
|
Widget _buildContinueButton(AssessmentViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
safe: false,
|
||||||
|
borderRadius: 12,
|
||||||
|
text: 'Continue Assessment',
|
||||||
|
onTap: () => viewModel.next(),
|
||||||
|
foregroundColor: kcWhite,
|
||||||
|
backgroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSkipButtonWrapper(AssessmentViewModel viewModel) => Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 50),
|
||||||
|
child: _buildSkipButton(viewModel),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget _buildSkipButton(AssessmentViewModel viewModel) =>
|
||||||
|
CustomElevatedButton(
|
||||||
|
height: 55,
|
||||||
|
text: 'Skip',
|
||||||
|
borderRadius: 12,
|
||||||
|
backgroundColor: kcWhite,
|
||||||
|
borderColor: kcPrimaryColor,
|
||||||
|
onTap: () => viewModel.next(),
|
||||||
|
foregroundColor: kcPrimaryColor,
|
||||||
|
);
|
||||||
|
}
|
||||||