commit e1f2206b8a5cb406b6a85e708fc334b47654dcbd Author: ferdiakhh Date: Thu Jul 10 19:15:14 2025 +0700 Initial commit: Penyerahan final Source code Tugas Akhir diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f008f8b --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +#env +.env +.env.dev +.env.prod + diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..3e6e02a --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: android + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: ios + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: linux + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: macos + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: web + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + - platform: windows + create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca77e6f --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Vocasia lms Mobile App + +Project ini dibuat untuk Tugas Akhir. +Nama : Muhammad Ferdi Akhdan +NIM : 2210501086 +Prodi : D3 Sistem Informasi UPNVJ + + +## Kebutuhan + +- flutter sdk version: "3.16.x" [download sdk](https://docs.flutter.dev/get-started/install) + +## Instalasi project + +- git clone --branch dev https://gitlab.com/divisi.it/vocasia-front-end-app.git +- cd vocasia-front-end-app +- flutter pub get + +## Run Project + +- command `flutter run` +- vscode `menu > run > run wth debugging/run without debugging` + +##### Tambahan + + Jika Ada error dari pusher_client + +Ubah versi kotlin pusher_client dari `ext.kotlin_version = '1.3.50'` menjadi `ext.kotlin_version = '1.7.10'` + +##### Langkah-langkah + +- buka folder pusher_client/android, `%AppData%\..\Local\Pub\Cache\hosted\pub.dev\pusher_client-2.0.0\android` +- buka file build.gradle, lalu cari `ext.kotlin_version` diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..d1fe768 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,71 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" + id 'com.google.gms.google-services' +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.initial_folder" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.initial_folder" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation platform('com.google.firebase:firebase-bom:32.7.0') + implementation 'com.google.firebase:firebase-analytics' +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..1a264e5 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,54 @@ +{ + "project_info": { + "project_number": "652715934272", + "project_id": "vocasia-bbfb5", + "storage_bucket": "vocasia-bbfb5.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:652715934272:android:6069e948b9052d2d3195a5", + "android_client_info": { + "package_name": "com.example.initial_folder" + } + }, + "oauth_client": [ + { + "client_id": "652715934272-scils5j9h4ijtgvdoqbmeg2ij79nrsm8.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.example.initial_folder", + "certificate_hash": "59c38f7aa79f5983ac5829a66c225e306f47dc0b" + } + }, + { + "client_id": "652715934272-7tk330fm39jvfhlh66g96hnqdlkr0h0o.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyAiHFJENCvW1B8RClUfdZXwk1H6suWtGUU" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "652715934272-7tk330fm39jvfhlh66g96hnqdlkr0h0o.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "652715934272-4p5mpa6hj9b71pfv5nn6al7pf79qck4h.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.fcmtes" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..79deb3c --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/initial_folder/MainActivity.kt b/android/app/src/main/kotlin/com/example/initial_folder/MainActivity.kt new file mode 100644 index 0000000..c1797c0 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/initial_folder/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.initial_folder + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable-hdpi/android12splash.png b/android/app/src/main/res/drawable-hdpi/android12splash.png new file mode 100644 index 0000000..5891c86 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png new file mode 100644 index 0000000..5891c86 Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-mdpi/android12splash.png b/android/app/src/main/res/drawable-mdpi/android12splash.png new file mode 100644 index 0000000..c0d3afd Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png new file mode 100644 index 0000000..c0d3afd Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/android/app/src/main/res/drawable-night-hdpi/android12splash.png new file mode 100644 index 0000000..9964e75 Binary files /dev/null and b/android/app/src/main/res/drawable-night-hdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-hdpi/splash.png b/android/app/src/main/res/drawable-night-hdpi/splash.png new file mode 100644 index 0000000..9964e75 Binary files /dev/null and b/android/app/src/main/res/drawable-night-hdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/android/app/src/main/res/drawable-night-mdpi/android12splash.png new file mode 100644 index 0000000..f628bbb Binary files /dev/null and b/android/app/src/main/res/drawable-night-mdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-mdpi/splash.png b/android/app/src/main/res/drawable-night-mdpi/splash.png new file mode 100644 index 0000000..f628bbb Binary files /dev/null and b/android/app/src/main/res/drawable-night-mdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-v21/background.png b/android/app/src/main/res/drawable-night-v21/background.png new file mode 100644 index 0000000..bb72a79 Binary files /dev/null and b/android/app/src/main/res/drawable-night-v21/background.png differ diff --git a/android/app/src/main/res/drawable-night-v21/launch_background.xml b/android/app/src/main/res/drawable-night-v21/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-night-v21/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png new file mode 100644 index 0000000..59ded5f Binary files /dev/null and b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xhdpi/splash.png b/android/app/src/main/res/drawable-night-xhdpi/splash.png new file mode 100644 index 0000000..59ded5f Binary files /dev/null and b/android/app/src/main/res/drawable-night-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png new file mode 100644 index 0000000..d44e350 Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxhdpi/splash.png b/android/app/src/main/res/drawable-night-xxhdpi/splash.png new file mode 100644 index 0000000..d44e350 Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png new file mode 100644 index 0000000..e9d4417 Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/splash.png b/android/app/src/main/res/drawable-night-xxxhdpi/splash.png new file mode 100644 index 0000000..e9d4417 Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-night/background.png b/android/app/src/main/res/drawable-night/background.png new file mode 100644 index 0000000..bb72a79 Binary files /dev/null and b/android/app/src/main/res/drawable-night/background.png differ diff --git a/android/app/src/main/res/drawable-night/launch_background.xml b/android/app/src/main/res/drawable-night/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-night/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-v21/background.png b/android/app/src/main/res/drawable-v21/background.png new file mode 100644 index 0000000..c7d9230 Binary files /dev/null and b/android/app/src/main/res/drawable-v21/background.png differ diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/drawable-xhdpi/android12splash.png b/android/app/src/main/res/drawable-xhdpi/android12splash.png new file mode 100644 index 0000000..aef5371 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png new file mode 100644 index 0000000..aef5371 Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxhdpi/android12splash.png new file mode 100644 index 0000000..7678d27 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xxhdpi/splash.png b/android/app/src/main/res/drawable-xxhdpi/splash.png new file mode 100644 index 0000000..7678d27 Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png new file mode 100644 index 0000000..a94f17b Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png differ diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png new file mode 100644 index 0000000..a94f17b Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png new file mode 100644 index 0000000..c7d9230 Binary files /dev/null and b/android/app/src/main/res/drawable/background.png differ diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..3cc4948 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..5108125 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..cb5175a Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6c3ae32 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..66a0beb Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..a678752 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml new file mode 100644 index 0000000..50f260f --- /dev/null +++ b/android/app/src/main/res/values-night-v31/styles.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..dbc9ea9 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml new file mode 100644 index 0000000..511e438 --- /dev/null +++ b/android/app/src/main/res/values-v31/styles.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..0d1fa8f --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..554e2b8 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.9.0' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "com.google.gms:google-services:4.3.15" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..7cd7128 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,29 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false +} + +include ":app" diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/fonts/.gitkeep b/assets/fonts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/fonts/NotoSans-Regular.ttf b/assets/fonts/NotoSans-Regular.ttf new file mode 100644 index 0000000..10589e2 Binary files /dev/null and b/assets/fonts/NotoSans-Regular.ttf differ diff --git a/assets/fonts/Poppins-ExtraLight.ttf b/assets/fonts/Poppins-ExtraLight.ttf new file mode 100644 index 0000000..e76ec69 Binary files /dev/null and b/assets/fonts/Poppins-ExtraLight.ttf differ diff --git a/assets/icons/.gitkeep b/assets/icons/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/icons/arrow_back.svg b/assets/icons/arrow_back.svg new file mode 100644 index 0000000..cc850e8 --- /dev/null +++ b/assets/icons/arrow_back.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/arrow_back_dark.svg b/assets/icons/arrow_back_dark.svg new file mode 100644 index 0000000..dfc2575 --- /dev/null +++ b/assets/icons/arrow_back_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/calendar.svg b/assets/icons/calendar.svg new file mode 100644 index 0000000..1fd893e --- /dev/null +++ b/assets/icons/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/cart.svg b/assets/icons/cart.svg new file mode 100644 index 0000000..a70e514 --- /dev/null +++ b/assets/icons/cart.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/cart_checklist.svg b/assets/icons/cart_checklist.svg new file mode 100644 index 0000000..b74aaf4 --- /dev/null +++ b/assets/icons/cart_checklist.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/cart_checklist_dark.svg b/assets/icons/cart_checklist_dark.svg new file mode 100644 index 0000000..d4fc3f9 --- /dev/null +++ b/assets/icons/cart_checklist_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/cart_dark.svg b/assets/icons/cart_dark.svg new file mode 100644 index 0000000..aad5b05 --- /dev/null +++ b/assets/icons/cart_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/cart_gunakan.png b/assets/icons/cart_gunakan.png new file mode 100644 index 0000000..8f4dcea Binary files /dev/null and b/assets/icons/cart_gunakan.png differ diff --git a/assets/icons/cart_remove.svg b/assets/icons/cart_remove.svg new file mode 100644 index 0000000..ecb9d2d --- /dev/null +++ b/assets/icons/cart_remove.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/cart_unchecklist.svg b/assets/icons/cart_unchecklist.svg new file mode 100644 index 0000000..32525f4 --- /dev/null +++ b/assets/icons/cart_unchecklist.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/cart_wishlist.svg b/assets/icons/cart_wishlist.svg new file mode 100644 index 0000000..cd43941 --- /dev/null +++ b/assets/icons/cart_wishlist.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/category1.svg b/assets/icons/category1.svg new file mode 100644 index 0000000..9023870 --- /dev/null +++ b/assets/icons/category1.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/icons/category2.svg b/assets/icons/category2.svg new file mode 100644 index 0000000..64e4d48 --- /dev/null +++ b/assets/icons/category2.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/category3.svg b/assets/icons/category3.svg new file mode 100644 index 0000000..447e640 --- /dev/null +++ b/assets/icons/category3.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/category4.svg b/assets/icons/category4.svg new file mode 100644 index 0000000..df1e185 --- /dev/null +++ b/assets/icons/category4.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/assets/icons/category5.svg b/assets/icons/category5.svg new file mode 100644 index 0000000..295845d --- /dev/null +++ b/assets/icons/category5.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/category6.svg b/assets/icons/category6.svg new file mode 100644 index 0000000..26b5683 --- /dev/null +++ b/assets/icons/category6.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/category7.svg b/assets/icons/category7.svg new file mode 100644 index 0000000..b1ac97e --- /dev/null +++ b/assets/icons/category7.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/category8.svg b/assets/icons/category8.svg new file mode 100644 index 0000000..06113dd --- /dev/null +++ b/assets/icons/category8.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/category9.svg b/assets/icons/category9.svg new file mode 100644 index 0000000..b06da5e --- /dev/null +++ b/assets/icons/category9.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/certificate.svg b/assets/icons/certificate.svg new file mode 100644 index 0000000..cc90b34 --- /dev/null +++ b/assets/icons/certificate.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/checklist.svg b/assets/icons/checklist.svg new file mode 100644 index 0000000..5b57e51 --- /dev/null +++ b/assets/icons/checklist.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/clock.svg b/assets/icons/clock.svg new file mode 100644 index 0000000..88843d6 --- /dev/null +++ b/assets/icons/clock.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/download.svg b/assets/icons/download.svg new file mode 100644 index 0000000..23f38ee --- /dev/null +++ b/assets/icons/download.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/eye.svg b/assets/icons/eye.svg new file mode 100644 index 0000000..ea204aa --- /dev/null +++ b/assets/icons/eye.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/facebook.png b/assets/icons/facebook.png new file mode 100644 index 0000000..69edf81 Binary files /dev/null and b/assets/icons/facebook.png differ diff --git a/assets/icons/featured.svg b/assets/icons/featured.svg new file mode 100644 index 0000000..6ab74da --- /dev/null +++ b/assets/icons/featured.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/featured_click.svg b/assets/icons/featured_click.svg new file mode 100644 index 0000000..5676aa8 --- /dev/null +++ b/assets/icons/featured_click.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/gold-medal.png b/assets/icons/gold-medal.png new file mode 100644 index 0000000..c8250cc Binary files /dev/null and b/assets/icons/gold-medal.png differ diff --git a/assets/icons/google.png b/assets/icons/google.png new file mode 100644 index 0000000..7fe55d1 Binary files /dev/null and b/assets/icons/google.png differ diff --git a/assets/icons/heart.svg b/assets/icons/heart.svg new file mode 100644 index 0000000..d2aa804 --- /dev/null +++ b/assets/icons/heart.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/heart_dark.svg b/assets/icons/heart_dark.svg new file mode 100644 index 0000000..0cc215c --- /dev/null +++ b/assets/icons/heart_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/heart_dark_select.svg b/assets/icons/heart_dark_select.svg new file mode 100644 index 0000000..f9f574d --- /dev/null +++ b/assets/icons/heart_dark_select.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/heart_select.svg b/assets/icons/heart_select.svg new file mode 100644 index 0000000..f584e84 --- /dev/null +++ b/assets/icons/heart_select.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/home_coupon.svg b/assets/icons/home_coupon.svg new file mode 100644 index 0000000..a3fb0fd --- /dev/null +++ b/assets/icons/home_coupon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/home_sertif.svg b/assets/icons/home_sertif.svg new file mode 100644 index 0000000..5abe4cb --- /dev/null +++ b/assets/icons/home_sertif.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/left-arrow.png b/assets/icons/left-arrow.png new file mode 100644 index 0000000..ae01a7a Binary files /dev/null and b/assets/icons/left-arrow.png differ diff --git a/assets/icons/lesson.svg b/assets/icons/lesson.svg new file mode 100644 index 0000000..bfc1449 --- /dev/null +++ b/assets/icons/lesson.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/lms/FileArchive.png b/assets/icons/lms/FileArchive.png new file mode 100644 index 0000000..30d0bc0 Binary files /dev/null and b/assets/icons/lms/FileArchive.png differ diff --git a/assets/icons/lms/FileDoc.png b/assets/icons/lms/FileDoc.png new file mode 100644 index 0000000..0f3859a Binary files /dev/null and b/assets/icons/lms/FileDoc.png differ diff --git a/assets/icons/lms/FileImage.png b/assets/icons/lms/FileImage.png new file mode 100644 index 0000000..b66aeba Binary files /dev/null and b/assets/icons/lms/FileImage.png differ diff --git a/assets/icons/lms/FilePdf.png b/assets/icons/lms/FilePdf.png new file mode 100644 index 0000000..1a24df6 Binary files /dev/null and b/assets/icons/lms/FilePdf.png differ diff --git a/assets/icons/lms/FilePpt.png b/assets/icons/lms/FilePpt.png new file mode 100644 index 0000000..50d8480 Binary files /dev/null and b/assets/icons/lms/FilePpt.png differ diff --git a/assets/icons/lms/FileText.png b/assets/icons/lms/FileText.png new file mode 100644 index 0000000..8627dac Binary files /dev/null and b/assets/icons/lms/FileText.png differ diff --git a/assets/icons/lms/FileXls.png b/assets/icons/lms/FileXls.png new file mode 100644 index 0000000..d45c2a0 Binary files /dev/null and b/assets/icons/lms/FileXls.png differ diff --git a/assets/icons/lms/FileZip.png b/assets/icons/lms/FileZip.png new file mode 100644 index 0000000..d78d402 Binary files /dev/null and b/assets/icons/lms/FileZip.png differ diff --git a/assets/icons/lms/ListNumbers.png b/assets/icons/lms/ListNumbers.png new file mode 100644 index 0000000..fd52a0d Binary files /dev/null and b/assets/icons/lms/ListNumbers.png differ diff --git a/assets/icons/moon.svg b/assets/icons/moon.svg new file mode 100644 index 0000000..b4ffe1d --- /dev/null +++ b/assets/icons/moon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/my_course.svg b/assets/icons/my_course.svg new file mode 100644 index 0000000..fba5b67 --- /dev/null +++ b/assets/icons/my_course.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/my_course_click.svg b/assets/icons/my_course_click.svg new file mode 100644 index 0000000..1171afd --- /dev/null +++ b/assets/icons/my_course_click.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/not_checklist.svg b/assets/icons/not_checklist.svg new file mode 100644 index 0000000..dae58d5 --- /dev/null +++ b/assets/icons/not_checklist.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/icons/notification.svg b/assets/icons/notification.svg new file mode 100644 index 0000000..154281e --- /dev/null +++ b/assets/icons/notification.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/notification_dark.svg b/assets/icons/notification_dark.svg new file mode 100644 index 0000000..3e147be --- /dev/null +++ b/assets/icons/notification_dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/open-book.png b/assets/icons/open-book.png new file mode 100644 index 0000000..9246ee1 Binary files /dev/null and b/assets/icons/open-book.png differ diff --git a/assets/icons/phone.svg b/assets/icons/phone.svg new file mode 100644 index 0000000..615eca8 --- /dev/null +++ b/assets/icons/phone.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/profile.svg b/assets/icons/profile.svg new file mode 100644 index 0000000..06002fc --- /dev/null +++ b/assets/icons/profile.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/profile_click.svg b/assets/icons/profile_click.svg new file mode 100644 index 0000000..96d8c37 --- /dev/null +++ b/assets/icons/profile_click.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/search.svg b/assets/icons/search.svg new file mode 100644 index 0000000..5915157 --- /dev/null +++ b/assets/icons/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/search_click.svg b/assets/icons/search_click.svg new file mode 100644 index 0000000..f2ecadf --- /dev/null +++ b/assets/icons/search_click.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/sertifikat_saya.png b/assets/icons/sertifikat_saya.png new file mode 100644 index 0000000..0a6cd14 Binary files /dev/null and b/assets/icons/sertifikat_saya.png differ diff --git a/assets/icons/student.svg b/assets/icons/student.svg new file mode 100644 index 0000000..78d4809 --- /dev/null +++ b/assets/icons/student.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/sun.svg b/assets/icons/sun.svg new file mode 100644 index 0000000..59d329f --- /dev/null +++ b/assets/icons/sun.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/verifikasi_sertifikat.png b/assets/icons/verifikasi_sertifikat.png new file mode 100644 index 0000000..cb1f7d3 Binary files /dev/null and b/assets/icons/verifikasi_sertifikat.png differ diff --git a/assets/icons/voucher.svg b/assets/icons/voucher.svg new file mode 100644 index 0000000..4aaa587 --- /dev/null +++ b/assets/icons/voucher.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wishlist.svg b/assets/icons/wishlist.svg new file mode 100644 index 0000000..f740782 --- /dev/null +++ b/assets/icons/wishlist.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wishlist_click.svg b/assets/icons/wishlist_click.svg new file mode 100644 index 0000000..94ee0fd --- /dev/null +++ b/assets/icons/wishlist_click.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/wrong.svg b/assets/icons/wrong.svg new file mode 100644 index 0000000..ded89e0 --- /dev/null +++ b/assets/icons/wrong.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/images/.gitkeep b/assets/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/assets/images/BgVector1.png b/assets/images/BgVector1.png new file mode 100644 index 0000000..2395654 Binary files /dev/null and b/assets/images/BgVector1.png differ diff --git a/assets/images/Kur1.png b/assets/images/Kur1.png new file mode 100644 index 0000000..a30fc1c Binary files /dev/null and b/assets/images/Kur1.png differ diff --git a/assets/images/Kur2.png b/assets/images/Kur2.png new file mode 100644 index 0000000..d957e3b Binary files /dev/null and b/assets/images/Kur2.png differ diff --git a/assets/images/Kur3.png b/assets/images/Kur3.png new file mode 100644 index 0000000..ddb818d Binary files /dev/null and b/assets/images/Kur3.png differ diff --git a/assets/images/Kur4.png b/assets/images/Kur4.png new file mode 100644 index 0000000..ea518ca Binary files /dev/null and b/assets/images/Kur4.png differ diff --git a/assets/images/Logo2.png b/assets/images/Logo2.png new file mode 100644 index 0000000..aec4eed Binary files /dev/null and b/assets/images/Logo2.png differ diff --git a/assets/images/Profile Image.png b/assets/images/Profile Image.png new file mode 100644 index 0000000..99b9091 Binary files /dev/null and b/assets/images/Profile Image.png differ diff --git a/assets/images/VOCASIA logo dark.png b/assets/images/VOCASIA logo dark.png new file mode 100644 index 0000000..4d9199c Binary files /dev/null and b/assets/images/VOCASIA logo dark.png differ diff --git a/assets/images/VOCASIA logo.png b/assets/images/VOCASIA logo.png new file mode 100644 index 0000000..0e8ee3f Binary files /dev/null and b/assets/images/VOCASIA logo.png differ diff --git a/assets/images/VOCASIA_ICON_BIG_DARK.png b/assets/images/VOCASIA_ICON_BIG_DARK.png new file mode 100644 index 0000000..46882ba Binary files /dev/null and b/assets/images/VOCASIA_ICON_BIG_DARK.png differ diff --git a/assets/images/VOCASIA_ICON_SM_DARK.png b/assets/images/VOCASIA_ICON_SM_DARK.png new file mode 100644 index 0000000..b04a342 Binary files /dev/null and b/assets/images/VOCASIA_ICON_SM_DARK.png differ diff --git a/assets/images/VOCASIA_LOGO_SQUARE_DARK.png b/assets/images/VOCASIA_LOGO_SQUARE_DARK.png new file mode 100644 index 0000000..a5bbbc0 Binary files /dev/null and b/assets/images/VOCASIA_LOGO_SQUARE_DARK.png differ diff --git a/assets/images/VOCASIA_LOGO_SQUARE_LIGHT.png b/assets/images/VOCASIA_LOGO_SQUARE_LIGHT.png new file mode 100644 index 0000000..3b977c9 Binary files /dev/null and b/assets/images/VOCASIA_LOGO_SQUARE_LIGHT.png differ diff --git a/assets/images/VOCASIA_SPLASH.png b/assets/images/VOCASIA_SPLASH.png new file mode 100644 index 0000000..dbfdebc Binary files /dev/null and b/assets/images/VOCASIA_SPLASH.png differ diff --git a/assets/images/VOCASIA_SPLASH_DARK.png b/assets/images/VOCASIA_SPLASH_DARK.png new file mode 100644 index 0000000..c10d278 Binary files /dev/null and b/assets/images/VOCASIA_SPLASH_DARK.png differ diff --git a/assets/images/VectorBG.png b/assets/images/VectorBG.png new file mode 100644 index 0000000..72383db Binary files /dev/null and b/assets/images/VectorBG.png differ diff --git a/assets/images/a.png b/assets/images/a.png new file mode 100644 index 0000000..62ee826 Binary files /dev/null and b/assets/images/a.png differ diff --git a/assets/images/alfamart.png b/assets/images/alfamart.png new file mode 100644 index 0000000..7834a75 Binary files /dev/null and b/assets/images/alfamart.png differ diff --git a/assets/images/banklain.png b/assets/images/banklain.png new file mode 100644 index 0000000..70f296e Binary files /dev/null and b/assets/images/banklain.png differ diff --git a/assets/images/bca.png b/assets/images/bca.png new file mode 100644 index 0000000..fdc86f3 Binary files /dev/null and b/assets/images/bca.png differ diff --git a/assets/images/bni.png b/assets/images/bni.png new file mode 100644 index 0000000..06bea05 Binary files /dev/null and b/assets/images/bni.png differ diff --git a/assets/images/bri.png b/assets/images/bri.png new file mode 100644 index 0000000..48c7d14 Binary files /dev/null and b/assets/images/bri.png differ diff --git a/assets/images/certif_template.jpg b/assets/images/certif_template.jpg new file mode 100644 index 0000000..e849820 Binary files /dev/null and b/assets/images/certif_template.jpg differ diff --git a/assets/images/certif_template.svg b/assets/images/certif_template.svg new file mode 100644 index 0000000..1187f32 --- /dev/null +++ b/assets/images/certif_template.svg @@ -0,0 +1,5873 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/certif_template_new.png b/assets/images/certif_template_new.png new file mode 100644 index 0000000..69497b0 Binary files /dev/null and b/assets/images/certif_template_new.png differ diff --git a/assets/images/certificate.png b/assets/images/certificate.png new file mode 100644 index 0000000..a0a7127 Binary files /dev/null and b/assets/images/certificate.png differ diff --git a/assets/images/certificate_icon.png b/assets/images/certificate_icon.png new file mode 100644 index 0000000..2dfa3d1 Binary files /dev/null and b/assets/images/certificate_icon.png differ diff --git a/assets/images/certificate_icon_profile.png b/assets/images/certificate_icon_profile.png new file mode 100644 index 0000000..f156c0a Binary files /dev/null and b/assets/images/certificate_icon_profile.png differ diff --git a/assets/images/course_thumbnail_default_13 1.png b/assets/images/course_thumbnail_default_13 1.png new file mode 100644 index 0000000..417adfe Binary files /dev/null and b/assets/images/course_thumbnail_default_13 1.png differ diff --git a/assets/images/cover_dark.png b/assets/images/cover_dark.png new file mode 100644 index 0000000..b55d99e Binary files /dev/null and b/assets/images/cover_dark.png differ diff --git a/assets/images/cover_light.png b/assets/images/cover_light.png new file mode 100644 index 0000000..871a257 Binary files /dev/null and b/assets/images/cover_light.png differ diff --git a/assets/images/default_banner_2.png b/assets/images/default_banner_2.png new file mode 100644 index 0000000..296a50c Binary files /dev/null and b/assets/images/default_banner_2.png differ diff --git a/assets/images/deffault_banner.png b/assets/images/deffault_banner.png new file mode 100644 index 0000000..2ad64cf Binary files /dev/null and b/assets/images/deffault_banner.png differ diff --git a/assets/images/discount_coupon.png b/assets/images/discount_coupon.png new file mode 100644 index 0000000..fd43b8e Binary files /dev/null and b/assets/images/discount_coupon.png differ diff --git a/assets/images/doc_button.png b/assets/images/doc_button.png new file mode 100644 index 0000000..be20173 Binary files /dev/null and b/assets/images/doc_button.png differ diff --git a/assets/images/gold-mastercard.png b/assets/images/gold-mastercard.png new file mode 100644 index 0000000..ad308dd Binary files /dev/null and b/assets/images/gold-mastercard.png differ diff --git a/assets/images/gopay.png b/assets/images/gopay.png new file mode 100644 index 0000000..4ffe650 Binary files /dev/null and b/assets/images/gopay.png differ diff --git a/assets/images/gopay1.png b/assets/images/gopay1.png new file mode 100644 index 0000000..28d221e Binary files /dev/null and b/assets/images/gopay1.png differ diff --git a/assets/images/gopay2.png b/assets/images/gopay2.png new file mode 100644 index 0000000..a92e328 Binary files /dev/null and b/assets/images/gopay2.png differ diff --git a/assets/images/gopay3.png b/assets/images/gopay3.png new file mode 100644 index 0000000..391719c Binary files /dev/null and b/assets/images/gopay3.png differ diff --git a/assets/images/gopay4.svg b/assets/images/gopay4.svg new file mode 100644 index 0000000..9952a57 --- /dev/null +++ b/assets/images/gopay4.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/home_coupon.png b/assets/images/home_coupon.png new file mode 100644 index 0000000..6a81634 Binary files /dev/null and b/assets/images/home_coupon.png differ diff --git a/assets/images/home_sertif.png b/assets/images/home_sertif.png new file mode 100644 index 0000000..b4cf1bd Binary files /dev/null and b/assets/images/home_sertif.png differ diff --git a/assets/images/img_button.png b/assets/images/img_button.png new file mode 100644 index 0000000..9171d74 Binary files /dev/null and b/assets/images/img_button.png differ diff --git a/assets/images/indomaret.png b/assets/images/indomaret.png new file mode 100644 index 0000000..6604d1e Binary files /dev/null and b/assets/images/indomaret.png differ diff --git a/assets/images/intro_image.png b/assets/images/intro_image.png new file mode 100644 index 0000000..1543787 Binary files /dev/null and b/assets/images/intro_image.png differ diff --git a/assets/images/invoices.png b/assets/images/invoices.png new file mode 100644 index 0000000..744016e Binary files /dev/null and b/assets/images/invoices.png differ diff --git a/assets/images/invoices_dark.png b/assets/images/invoices_dark.png new file mode 100644 index 0000000..fdfb39a Binary files /dev/null and b/assets/images/invoices_dark.png differ diff --git a/assets/images/kredit.png b/assets/images/kredit.png new file mode 100644 index 0000000..0770136 Binary files /dev/null and b/assets/images/kredit.png differ diff --git a/assets/images/kursuskosong.png b/assets/images/kursuskosong.png new file mode 100644 index 0000000..9a31b24 Binary files /dev/null and b/assets/images/kursuskosong.png differ diff --git a/assets/images/mandiri.png b/assets/images/mandiri.png new file mode 100644 index 0000000..2851ab6 Binary files /dev/null and b/assets/images/mandiri.png differ diff --git a/assets/images/no_certif_preview.png b/assets/images/no_certif_preview.png new file mode 100644 index 0000000..16e10aa Binary files /dev/null and b/assets/images/no_certif_preview.png differ diff --git a/assets/images/no_certificate_preview.png b/assets/images/no_certificate_preview.png new file mode 100644 index 0000000..2518056 Binary files /dev/null and b/assets/images/no_certificate_preview.png differ diff --git a/assets/images/no_sertif_preview.png b/assets/images/no_sertif_preview.png new file mode 100644 index 0000000..36112b0 Binary files /dev/null and b/assets/images/no_sertif_preview.png differ diff --git a/assets/images/permata.png b/assets/images/permata.png new file mode 100644 index 0000000..ff7f320 Binary files /dev/null and b/assets/images/permata.png differ diff --git a/assets/images/permata_dark.png b/assets/images/permata_dark.png new file mode 100644 index 0000000..aa8d740 Binary files /dev/null and b/assets/images/permata_dark.png differ diff --git a/assets/images/play_button.png b/assets/images/play_button.png new file mode 100644 index 0000000..36025cf Binary files /dev/null and b/assets/images/play_button.png differ diff --git a/assets/images/play_button_new.png b/assets/images/play_button_new.png new file mode 100644 index 0000000..7520603 Binary files /dev/null and b/assets/images/play_button_new.png differ diff --git a/assets/images/ppt_button.png b/assets/images/ppt_button.png new file mode 100644 index 0000000..99ff0fa Binary files /dev/null and b/assets/images/ppt_button.png differ diff --git a/assets/images/prakerja.png b/assets/images/prakerja.png new file mode 100644 index 0000000..2a5c357 Binary files /dev/null and b/assets/images/prakerja.png differ diff --git a/assets/images/qris.png b/assets/images/qris.png new file mode 100644 index 0000000..c83b2bb Binary files /dev/null and b/assets/images/qris.png differ diff --git a/assets/images/qris_background.png b/assets/images/qris_background.png new file mode 100644 index 0000000..d1a650a Binary files /dev/null and b/assets/images/qris_background.png differ diff --git a/assets/images/quizLogo-page.png b/assets/images/quizLogo-page.png new file mode 100644 index 0000000..47711f3 Binary files /dev/null and b/assets/images/quizLogo-page.png differ diff --git a/assets/images/quiz_button.png b/assets/images/quiz_button.png new file mode 100644 index 0000000..cafca36 Binary files /dev/null and b/assets/images/quiz_button.png differ diff --git a/assets/images/quiz_result_benar.png b/assets/images/quiz_result_benar.png new file mode 100644 index 0000000..74d5482 Binary files /dev/null and b/assets/images/quiz_result_benar.png differ diff --git a/assets/images/quiz_result_salah.png b/assets/images/quiz_result_salah.png new file mode 100644 index 0000000..6d41ae9 Binary files /dev/null and b/assets/images/quiz_result_salah.png differ diff --git a/assets/images/rar_button.png b/assets/images/rar_button.png new file mode 100644 index 0000000..597172d Binary files /dev/null and b/assets/images/rar_button.png differ diff --git a/assets/images/search.png b/assets/images/search.png new file mode 100644 index 0000000..0ab2176 Binary files /dev/null and b/assets/images/search.png differ diff --git a/assets/images/sertif_btn.png b/assets/images/sertif_btn.png new file mode 100644 index 0000000..9213f40 Binary files /dev/null and b/assets/images/sertif_btn.png differ diff --git a/assets/images/shopee.png b/assets/images/shopee.png new file mode 100644 index 0000000..db1ef36 Binary files /dev/null and b/assets/images/shopee.png differ diff --git a/assets/images/success_pay.png b/assets/images/success_pay.png new file mode 100644 index 0000000..5edf205 Binary files /dev/null and b/assets/images/success_pay.png differ diff --git a/assets/images/switch.png b/assets/images/switch.png new file mode 100644 index 0000000..8e0e3c9 Binary files /dev/null and b/assets/images/switch.png differ diff --git a/assets/images/tshirt.png b/assets/images/tshirt.png new file mode 100644 index 0000000..e4f0cc6 Binary files /dev/null and b/assets/images/tshirt.png differ diff --git a/assets/images/undraw_certificate.png b/assets/images/undraw_certificate.png new file mode 100644 index 0000000..838b53c Binary files /dev/null and b/assets/images/undraw_certificate.png differ diff --git a/assets/images/visa.png b/assets/images/visa.png new file mode 100644 index 0000000..6037ecf Binary files /dev/null and b/assets/images/visa.png differ diff --git a/assets/images/visacard.png b/assets/images/visacard.png new file mode 100644 index 0000000..5f00761 Binary files /dev/null and b/assets/images/visacard.png differ diff --git a/assets/images/vocasia_logoAPK_new.png b/assets/images/vocasia_logoAPK_new.png new file mode 100644 index 0000000..20c2f8d Binary files /dev/null and b/assets/images/vocasia_logoAPK_new.png differ diff --git a/assets/images/workshop.png b/assets/images/workshop.png new file mode 100644 index 0000000..789e0b9 Binary files /dev/null and b/assets/images/workshop.png differ diff --git a/assets/images/xlsx_button.png b/assets/images/xlsx_button.png new file mode 100644 index 0000000..8df796a Binary files /dev/null and b/assets/images/xlsx_button.png differ diff --git a/assets/images/zip_button.png b/assets/images/zip_button.png new file mode 100644 index 0000000..7b9e0c1 Binary files /dev/null and b/assets/images/zip_button.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..5c27c3e --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,2 @@ +extensions: + - provider: true \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ac3a38f --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,614 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807E294A63A400263BE5 /* Frameworks */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0B7B92F70575B8D7E0D07E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 89B67EB44CE7B6631473024E /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 640959BDD8F10B91D80A66BE /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..87131a0 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png new file mode 100644 index 0000000..d2e2948 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png new file mode 100644 index 0000000..22d0611 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png new file mode 100644 index 0000000..25186fa Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png new file mode 100644 index 0000000..6410e90 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png new file mode 100644 index 0000000..c6290d6 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png new file mode 100644 index 0000000..e7f327f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png new file mode 100644 index 0000000..f02eedc Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png new file mode 100644 index 0000000..52f5e6b Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png new file mode 100644 index 0000000..fdd77a5 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png new file mode 100644 index 0000000..2716f43 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png new file mode 100644 index 0000000..5a836ed Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..73d3b7f --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1 @@ +{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]} \ No newline at end of file diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..9f87e6c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..f7139e1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..b6d9042 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..db33011 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..68f5ee4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..6a546d5 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..fc9cb02 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..b6d9042 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..627e334 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..9fcaef0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png new file mode 100644 index 0000000..ea3e715 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png new file mode 100644 index 0000000..4c444ef Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png new file mode 100644 index 0000000..552df5f Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png new file mode 100644 index 0000000..ee774b1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..9fcaef0 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..ca0faa1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png new file mode 100644 index 0000000..5108125 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png new file mode 100644 index 0000000..66a0beb Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..1ad8b66 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..bfba98c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..3c953f2 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json new file mode 100644 index 0000000..8bb185b --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "background.png", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "darkbackground.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png new file mode 100644 index 0000000..c7d9230 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png new file mode 100644 index 0000000..bb72a79 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..f3387d4 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "LaunchImage.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "LaunchImageDark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "LaunchImage@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "LaunchImageDark@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "LaunchImage@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "LaunchImageDark@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..c0d3afd Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..aef5371 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..7678d27 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png new file mode 100644 index 0000000..f628bbb Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png new file mode 100644 index 0000000..59ded5f Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png new file mode 100644 index 0000000..d44e350 Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImageDark@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..8d2b7d5 --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..d69c1ef --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 652715934272-89m63o0o412jr6irvf5pnurb7076ajlp.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.652715934272-89m63o0o412jr6irvf5pnurb7076ajlp + ANDROID_CLIENT_ID + 652715934272-8bgpnurmsj0lg8e6c9cg1ccogptm6erm.apps.googleusercontent.com + API_KEY + AIzaSyDVS6d5Y3GBQXVhk1HfL2GdiC1Wx251b_c + GCM_SENDER_ID + 652715934272 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.initialFolder + PROJECT_ID + vocasia-bbfb5 + STORAGE_BUCKET + vocasia-bbfb5.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:652715934272:ios:0620353fdfbf6d0a3195a5 + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..869f39b --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Initial Folder + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + initial_folder + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + UIStatusBarHidden + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/ios/firebase_app_id_file.json b/ios/firebase_app_id_file.json new file mode 100644 index 0000000..84566c4 --- /dev/null +++ b/ios/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:652715934272:ios:0620353fdfbf6d0a3195a5", + "FIREBASE_PROJECT_ID": "vocasia-bbfb5", + "GCM_SENDER_ID": "652715934272" +} \ No newline at end of file diff --git a/lib/base_service.dart b/lib/base_service.dart new file mode 100644 index 0000000..44667e2 --- /dev/null +++ b/lib/base_service.dart @@ -0,0 +1,17 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +// Ganti baseUrl jadi ngambil dari file .env +String? baseUrl = dotenv.env['BASE_URL_API']; + +const Map baseHeader = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', +}; + +Map headerWithToken(String? token) { + return { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer $token', + }; +} diff --git a/lib/components/.gitkeep b/lib/components/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..349253c --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,85 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + return macos; + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyDXtaFclWaNaXjMnPsLrsRzEya5c1Lx54U', + appId: '1:652715934272:web:8a2a174bdd075e9b3195a5', + messagingSenderId: '652715934272', + projectId: 'vocasia-bbfb5', + authDomain: 'vocasia-bbfb5.firebaseapp.com', + storageBucket: 'vocasia-bbfb5.appspot.com', + measurementId: 'G-W7Z8ESP739', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyAiHFJENCvW1B8RClUfdZXwk1H6suWtGUU', + appId: '1:652715934272:android:6069e948b9052d2d3195a5', + messagingSenderId: '652715934272', + projectId: 'vocasia-bbfb5', + storageBucket: 'vocasia-bbfb5.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyDVS6d5Y3GBQXVhk1HfL2GdiC1Wx251b_c', + appId: '1:652715934272:ios:0620353fdfbf6d0a3195a5', + messagingSenderId: '652715934272', + projectId: 'vocasia-bbfb5', + storageBucket: 'vocasia-bbfb5.appspot.com', + androidClientId: '652715934272-8bgpnurmsj0lg8e6c9cg1ccogptm6erm.apps.googleusercontent.com', + iosClientId: '652715934272-89m63o0o412jr6irvf5pnurb7076ajlp.apps.googleusercontent.com', + iosBundleId: 'com.example.initialFolder', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyDVS6d5Y3GBQXVhk1HfL2GdiC1Wx251b_c', + appId: '1:652715934272:ios:05922d3f99967a1b3195a5', + messagingSenderId: '652715934272', + projectId: 'vocasia-bbfb5', + storageBucket: 'vocasia-bbfb5.appspot.com', + androidClientId: '652715934272-8bgpnurmsj0lg8e6c9cg1ccogptm6erm.apps.googleusercontent.com', + iosClientId: '652715934272-4vdmmf2mdh5nb4j0lnutspkcceh8brdn.apps.googleusercontent.com', + iosBundleId: 'com.example.initialFolder.RunnerTests', + ); +} diff --git a/lib/get_it.dart b/lib/get_it.dart new file mode 100644 index 0000000..326254f --- /dev/null +++ b/lib/get_it.dart @@ -0,0 +1,26 @@ +import 'package:get_it/get_it.dart'; +import 'package:initial_folder/providers/announcement_provider.dart'; +import 'package:initial_folder/providers/detail_course_coupon_provider.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/qna_provider.dart'; +import 'package:initial_folder/providers/reply_announcement_provider.dart'; +import 'package:initial_folder/providers/reply_qna_provider.dart'; + +final qnaGetIt = GetIt.instance; +final replyQnaGetIt = GetIt.instance; +final detailGetIt = GetIt.instance; +final detailCouponGetIt = GetIt.instance; +final announcementGetIt = GetIt.instance; +final replyAnnouncementGetIt = GetIt.instance; + +void setup() { + qnaGetIt.registerSingleton(QnaProvider()); + replyQnaGetIt.registerSingleton(ReplyQnaProvider()); + detailGetIt.registerSingleton(DetailProvider()); + detailCouponGetIt + .registerSingleton(DetailCouponProvider()); + announcementGetIt + .registerSingleton(AnnouncementProvider()); + replyAnnouncementGetIt.registerSingleton( + ReplyAnnouncementProvider()); +} diff --git a/lib/helper/user_info.dart b/lib/helper/user_info.dart new file mode 100644 index 0000000..e72749f --- /dev/null +++ b/lib/helper/user_info.dart @@ -0,0 +1,73 @@ +import 'dart:async'; + +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + +class UsersInfo { + final storage = new FlutterSecureStorage(); + + Future setToken(String? value) async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.setString("token", value!); + } + + Future setRefreshToken(String? value) async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.setString('refresh_token', value!); + } + + Future setEmail(String? value) async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.setString("email", value!); + } + + Future setIdUser(int? value) async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.setInt('idUser', value!); + } + + setListData(String key, List value) async { + SharedPreferences myPrefs = await SharedPreferences.getInstance(); + myPrefs.setStringList(key, value); + } + + Future setStateintro(String? value) async { + await storage.write(key: 'intro', value: value); + } + + Future?> getListData(String key) async { + SharedPreferences myPrefs = await SharedPreferences.getInstance(); + return myPrefs.getStringList(key); + } + + Future getToken() async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.getString("token"); + } + + Future getRefreshToken() async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.getString("refresh_token"); + } + + Future getEmail() async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.getString("email"); + } + + Future getIdUser() async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + return pref.getInt("idUser"); + } + + Future getStateintro() async { + // String value = await storage.read(key: 'intro'); + print(await storage.read(key: 'intro')); + return await storage.read(key: 'intro'); + } + + Future logout() async { + final SharedPreferences pref = await SharedPreferences.getInstance(); + pref.clear(); + } +} diff --git a/lib/helper/validator.dart b/lib/helper/validator.dart new file mode 100644 index 0000000..eacdc52 --- /dev/null +++ b/lib/helper/validator.dart @@ -0,0 +1,368 @@ +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:recase/recase.dart'; +import 'package:flutter/material.dart'; + +String? validateName(String? value) { + if (value!.isEmpty) { + return 'Nama lengkap tidak boleh kosong'; + } else if (!RegExp(r'^[a-zA-Z]+(?: [a-zA-Z]+)*$').hasMatch(value)) { + return 'Format Nama Tidak Valid'; + } else { + return null; + } +} + +String? validatePassword(String? value) { + if (value!.isEmpty) { + return 'Password tidak boleh kosong'; + } else if (value.length < 8) { + return 'Password Tidak Sesuai'; + } else { + return null; + } +} + +String? validateRePassword(String? value, String password) { + if (value!.isEmpty) { + return 'Password tidak boleh kosong'; + } else if (value.length < 8) { + return 'Password minimal harus berjumlah 8 karakter'; + } else if (value != password) { + return 'Password tidak sama'; + } + return null; +} + +String? validateEmail(String? value) { + Pattern pattern = + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; + RegExp regex = RegExp(pattern.toString()); + if (value!.isEmpty) { + return 'Email tidak boleh kosong'; + } else if (!regex.hasMatch(value)) { + return 'Mohon masukkan email yang valid'; + } else { + return null; + } +} + +String? validateReEmail(String? value, String email) { + Pattern pattern = + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; + RegExp regex = RegExp(pattern.toString()); + if (value!.isEmpty) { + return 'Konfirmasi email tidak boleh kosong'; + } else if (!regex.hasMatch(value)) { + return 'Mohon masukkan konfirmasi email yang valid'; + } else if (value != email) { + return 'Email tidak sama'; + } else { + return null; + } +} + +String? validatePhone(String? value) { + Pattern pattern = r'^[0][0-9]{9,30}$'; + RegExp regex = RegExp(pattern.toString()); + if (value!.isEmpty) { + return 'Nomor telepon tidak boleh kosong'; + } else if (!regex.hasMatch(value)) { + return 'Mohon masukkan nomor telepon yang valid'; + } else if (value!.length > 15) { + return 'Nomor telepon maksimal 15 karakter'; + } else { + return null; + } +} + +String? validateNomorKartu(String? value) { + if (value!.length != 22) { + return 'Panjang Nomor Kartu harus 16 karakter'; + } else { + return null; + } +} + +String? validateCoupon(String? value) { + if (value!.contains(' ')) { + return 'Harap masukkan kupon tanpa spasi'; + } else { + return null; + } +} + +String? validateMasaBerlaku(String? value) { + if (value!.length != 5) { + return 'Data belum lengkap'; + } else { + return null; + } +} + +String? validateKodeKeamanan(String? value) { + if (value!.length != 3) { + return 'Panjang Kode Keamanan harus 3 karakter'; + } else { + return null; + } +} + +String? validateDate(String? value) { + if (value!.isEmpty) { + return 'Data tidak boleh kosong'; + } + + int year; + int month; + // The value contains a forward slash if the month and year has been + // entered. + if (value.contains(new RegExp(r'(\/)'))) { + var split = value.split(new RegExp(r'(\/)')); + // The value before the slash is the month while the value to right of + // it is the year. + month = int.parse(split[0]); + year = int.parse(split[1]); + } else { + // Only the month was entered + month = int.parse(value.substring(0, (value.length))); + year = -1; // Lets use an invalid year intentionally + } + + if ((month < 1) || (month > 12)) { + // A valid month is between 1 (January) and 12 (December) + return 'Masukkan Bulan yang valid'; + } + + var fourDigitsYear = convertYearTo4Digits(year); + if ((fourDigitsYear < 1) || (fourDigitsYear > 2099)) { + // We are assuming a valid year should be between 1 and 2099. + // Note that, it's valid doesn't mean that it has not expired. + return 'Masukkan Tahun yang valid'; + } + + if (!hasDateExpired(month, year)) { + return "Kartu telah kadaluarsa"; + } + return null; +} + +/// Convert the two-digit year to four-digit year if necessary +int convertYearTo4Digits(int year) { + if (year < 100 && year >= 0) { + var now = DateTime.now(); + String currentYear = now.year.toString(); + String prefix = currentYear.substring(0, currentYear.length - 2); + year = int.parse('$prefix${year.toString().padLeft(2, '0')}'); + } + return year; +} + +bool hasDateExpired(int? month, int? year) { + return !(month == null || year == null) && isNotExpired(year, month); +} + +bool isNotExpired(int year, int month) { + // It has not expired if both the year and date has not passed + return !hasYearPassed(year) && !hasMonthPassed(year, month); +} + +bool hasMonthPassed(int year, int month) { + var now = DateTime.now(); + // The month has passed if: + // 1. The year is in the past. In that case, we just assume that the month + // has passed + // 2. Card's month (plus another month) is less than current month. + return hasYearPassed(year) || + convertYearTo4Digits(year) == now.year && (month < now.month + 1); +} + +bool hasYearPassed(int year) { + int fourDigitsYear = convertYearTo4Digits(year); + var now = DateTime.now(); + // The year has passed if the year we are currently, is greater than card's + // year + return fourDigitsYear < now.year; +} + +String cleanTagHtml(String htmlText) { + RegExp exp = RegExp(r"<[^>]*>", multiLine: true, caseSensitive: true); + + return htmlText.replaceAll(exp, ''); +} + +String filterDuration(String str) { + str = str.replaceAll('m', ' menit '); + str = str.replaceAll('j', ' jam '); + str = str.replaceAll('d', ' detik video pembelajaran'); + return str; +} + +// numberFormat(String? discountPrice) { +// return NumberFormat.currency( +// locale: 'id', +// decimalDigits: 0, +// symbol: 'Rp. ', +// ).format(int.parse(discountPrice ?? '0')); +// } +String? birthDateValidator(String? value) { + final DateTime now = DateTime.now(); + final DateFormat formatter = DateFormat('yyyy'); + final String formatted = formatter.format(now); + String? str1 = value; + List str2 = str1!.split('/'); + String month = str2.isNotEmpty ? str2[0] : ''; + String day = str2.length > 1 ? str2[1] : ''; + String year = str2.length > 2 ? str2[2] : ''; + if (value!.isEmpty) { + return 'BirthDate is Empty'; + } else if (int.parse(month) > 13) { + return 'Month is invalid'; + } else if (int.parse(day) > 32) { + return 'Day is invalid'; + } else if ((int.parse(year) > int.parse(formatted))) { + return 'Year is invalid'; + } else if ((int.parse(year) < 1920)) { + return 'Year is invalid'; + } + return null; +} + +String validatorSearch(String inputSearch) { + print("ini search"); + inputSearch = inputSearch.trimLeft(); + inputSearch = inputSearch.replaceAll(' ', '%'); + + return inputSearch; +} + +String validatorSearchFilter(String inputSearch) { + print("ini search"); + inputSearch = inputSearch.trimLeft(); + inputSearch = inputSearch.replaceAll(' ', '%'); + + return inputSearch; +} + +String dateFormatUlasan(String date) { + date = date.replaceAll(' ', '-'); + date = date.replaceAll('Januari', 'Jan'); + date = date.replaceAll('Februari', 'Feb'); + date = date.replaceAll('Maret', 'Mar'); + date = date.replaceAll('April', 'Apr'); + date = date.replaceAll('Juni', 'Jun'); + date = date.replaceAll('Juli', 'Jul'); + date = date.replaceAll('Agustus', 'Agu'); + date = date.replaceAll('September', 'Sep'); + date = date.replaceAll('Oktober', 'Okt'); + date = date.replaceAll('November', 'Nov'); + date = date.replaceAll('Desember', 'Des'); + return date; +} + +String formatAktivitas(String input) { + if (input.length >= 6) { + input = input.replaceAll('m', ':'); + input = input.replaceAll('j', ':'); + input = input.replaceAll('d', ''); + return input = '0' + input; + } else { + input = input.replaceAll('m', ':'); + input = input.replaceAll('j', ':'); + input = input.replaceAll('d', ''); + } + + return input; +} + +String persentaseUlasan(String input) { + input = input.replaceAll('%', ''); + if (input.length > 4) { + input = input.replaceAll('.', ''); + input = input.substring(0, 2); + } + return input; +} + +String iconCategory(String input) { + input = input.replaceAll('fas fa-', ''); + + return input.camelCase; +} + +// late List _words; +// String _upperCaseFirstLetter(String word) { +// return '${word.substring(0, 1).toUpperCase()}${word.substring(1).toLowerCase()}'; +// } + +// String getCamelCase({String separator: ''}) { +// List words = _words.map(_upperCaseFirstLetter).toList(); +// if (_words.isNotEmpty) { +// words[0] = words[0].toLowerCase(); +// } + +// return words.join(separator); +// } +// List words(String subject, [Pattern customPattern = defaultPattern]) { +// if (subject is! String || subject.length == 0) { +// return []; +// } + +// RegExp pattern; + +// if (customPattern is String) { +// pattern = RegExp(customPattern); +// } else if (customPattern is RegExp) { +// pattern = customPattern; +// } + +// return pattern.allMatches(subject).map((m) => m.group(0)).toList(); +// } +// String camelCase(String subject) { +// List _splittedString = words(subject); + +// if (_splittedString.length == 0) { +// return ''; +// } + +// String _firstWord = lowerCase(_splittedString[0]); +// List _restWords = _splittedString +// .sublist(1) +// .map((String x) => capitalize(x, true)) +// .toList(); + +// return _firstWord + _restWords.join(''); +// } + +String filterAnd(String input) { + input = input.replaceAll('&', '&'); + return input; +} + +numberFormat(String? discountPrice) { + return NumberFormat.currency( + locale: 'id', + decimalDigits: 0, + symbol: 'Rp ', + ).format(int.parse(discountPrice ?? '0')); +} + +customTextValidator(bool? isTextEmpty, String validatorText) { + return Text( + isTextEmpty == null + ? '' + : isTextEmpty + ? " $validatorText" + : '', + style: primaryTextStyle.copyWith( + color: Colors.red[700], + fontSize: 12.5, + height: isTextEmpty == null + ? 0 + : isTextEmpty + ? null + : 0, + ), + textAlign: TextAlign.start, + ); +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..967e9d8 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,348 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:initial_folder/firebase_options.dart'; +import 'package:initial_folder/get_it.dart'; +import 'package:initial_folder/providers/announcement_provider.dart'; +import 'package:initial_folder/providers/auth_provider.dart'; +import 'package:initial_folder/providers/banners_provider.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/categories_provider.dart'; +import 'package:initial_folder/providers/certificate_provider.dart'; +import 'package:initial_folder/providers/checkbox_provider.dart'; +import 'package:initial_folder/providers/data_diri_provider.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/filters_course_provider.dart'; +import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +import 'package:initial_folder/providers/forgot_password_provider.dart'; +import 'package:initial_folder/providers/incomplete_profile_provider.dart'; +import 'package:initial_folder/providers/history_transactions_provider.dart'; +import 'package:initial_folder/providers/latest_course_provider.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/like_announcement.dart'; +import 'package:initial_folder/providers/like_or_unlike_provider.dart'; +import 'package:initial_folder/providers/login_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/notification_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/others_course_provider.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/providers/play_video_course_provider.dart'; +import 'package:initial_folder/providers/posting_announcement_reply_provider.dart'; +import 'package:initial_folder/providers/posting_qna_provider.dart'; +import 'package:initial_folder/providers/posting_qna_reply_provider.dart'; +import 'package:initial_folder/providers/profile_image_provider.dart'; +import 'package:initial_folder/providers/promo_course_provider.dart'; +import 'package:initial_folder/providers/radeem_voucher_provider.dart'; +import 'package:initial_folder/providers/registrasi_google_provider.dart'; +import 'package:initial_folder/providers/reset_provider.dart'; +import 'package:initial_folder/providers/search_provider.dart'; +import 'package:initial_folder/providers/section_lesson_provider.dart'; +import 'package:initial_folder/providers/selected_title_provider.dart'; +import 'package:initial_folder/providers/stream_invoice_provider.dart'; +import 'package:initial_folder/providers/tab_play_course_provider.dart'; +import 'package:initial_folder/providers/description_provider.dart'; +import 'package:initial_folder/providers/metode_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/providers/profile_provider.dart'; +import 'package:initial_folder/providers/tab_provider.dart'; +import 'package:initial_folder/providers/top_course_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/providers/update_data_diri_provider.dart'; +import 'package:initial_folder/providers/update_password_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +// import 'package:initial_folder/screens/home/components/notification.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/services/all_certificate_service.dart'; +import 'package:initial_folder/services/announcement_service.dart'; +import 'package:initial_folder/services/banners_service.dart'; +import 'package:initial_folder/services/cart_service.dart'; +import 'package:initial_folder/services/categories_service.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/history_transactions_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/services/notification_service.dart'; +import 'package:initial_folder/services/payment_service.dart'; +import 'package:initial_folder/services/search_service.dart'; +import 'package:initial_folder/services/user_info_service.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/routes.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:responsive_framework/responsive_framework.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/providers/detail_invoice_provider.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +// Import library dan provider yang dibutuhkan untuk seluruh aplikasi + +// Fungsi handler untuk pesan notifikasi Firebase yang diterima saat aplikasi di background +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // Jika ingin menggunakan service Firebase lain di background, inisialisasi dulu + // print pesan jika mode debug +} + +final globalScaffoldKey = GlobalKey(); +GlobalKey navigatorKey = GlobalKey(); +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]); + + await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); + await FirebaseMessaging.instance.getToken(); + setup(); + + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + FirebaseMessaging.onMessageOpenedApp.listen((event) { + if (kDebugMode) { + print(event); + } + if (event.data['route'] == "/cart") { + navigatorKey.currentState?.push(MaterialPageRoute( + builder: (context) => const CartPage(), + )); + } else if (event.data['route'] == "/play_course") { + navigatorKey.currentState?.push( + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => LessonCourseProvider( + lessonCourseService: LessonCourseService(), + id: int.parse(event.data['id_course'] ?? '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), + id: event.data['id_course'] ?? '1')) + ], + child: PlayCourse( + judul: event.data['title'] ?? '', + instruktur: event.data['instructur_name'] ?? '', + thumbnail: event.data['thumbnail'] ?? + "$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg", + courseeid: event.data['id_course'], + isQna: true, + ), + ), + ), + ); + } + }); + ThemeProvider themeProvider = ThemeProvider(); + await themeProvider.loadTheme(); + runApp(MyApp(themeProvider: themeProvider)); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key, required this.themeProvider}) : super(key: key); + + final ThemeProvider themeProvider; + + @override + Widget build(BuildContext context) { + // Provider untuk Theme agar bisa diakses seluruh aplikasi + return ChangeNotifierProvider.value( + value: themeProvider, + child: MultiProvider( + // MultiProvider: daftar semua provider yang digunakan di aplikasi + providers: [ + ChangeNotifierProvider( + create: (context) => ThemeProvider(), + ), + ChangeNotifierProvider( + create: (context) => StreamInvoiceProvider(), + ), + ChangeNotifierProvider( + create: (context) => TabProvider(), + ), + ChangeNotifierProvider( + create: (context) => DetailInvoiceProvider(), + ), + ChangeNotifierProvider( + create: (context) => SelectedTitleProvider(), + ), + ChangeNotifierProvider( + create: (context) => LikeOrUnlikeProvider(), + ), + ChangeNotifierProvider( + create: (context) => LikeOrAnnouncementProvider(), + ), + ChangeNotifierProvider( + create: (context) => AnnouncementProvider(), + ), + ChangeNotifierProvider( + create: (context) => PostingQnaReplyProvider(), + ), + ChangeNotifierProvider( + create: (context) => PostingAnnouncementReplyProvider(), + ), + ChangeNotifierProvider( + create: (context) => ForgotPasswordProvider(), + ), + ChangeNotifierProvider( + create: (context) => PostingQnaProvider(), + ), + ChangeNotifierProvider( + create: (context) => DataDiriProvider( + userInfoService: UserInfoService(), + ), + ), + ChangeNotifierProvider( + create: (context) => UpdateDataDiriProvider(), + ), + ChangeNotifierProvider( + create: (context) => UpdatePasswordProvider(), + ), + ChangeNotifierProvider( + create: (context) => TabPlayCourseProvider(), + ), + ChangeNotifierProvider( + create: (context) => SectionLessonProvider(), + ), + ChangeNotifierProvider( + create: (context) => PageProvider(), + ), + ChangeNotifierProvider( + create: (context) => ProfileImageProvider(), + ), + ChangeNotifierProvider( + create: (context) => WishlistPostProvider(), + ), + ChangeNotifierProvider( + create: (context) => ProfileProvider(), + ), + ChangeNotifierProvider( + create: (context) => CheckboxProvider(), + ), + ChangeNotifierProvider( + create: (context) => UserInfoProvider( + userInfoService: UserInfoService(), + )), + ChangeNotifierProvider( + create: (context) => NotificationProvider( + notificationServices: NotificationServices(), + )), + ChangeNotifierProvider( + create: (context) => DescriptionProvider(), + ), + ChangeNotifierProvider( + create: (context) => TotalPriceProvider(), + ), + ChangeNotifierProvider( + create: (context) => MetodeProvider(), + ), + ChangeNotifierProvider( + create: (context) => FirebaseAuthenticationProvider(), + ), + ChangeNotifierProvider( + create: (context) => RegistrasiGoogleProvider(), + ), + ChangeNotifierProvider( + create: (context) => RegistrasiGoogleProvider(), + ), + ChangeNotifierProvider(create: (context) => ResetProvider()), + ChangeNotifierProvider( + create: (context) => AuthProvider(), + ), + ChangeNotifierProvider( + create: (context) => + CategoriesProvider(categoriesService: CategoriesService()), + ), + ChangeNotifierProvider( + create: (context) => + BannersProvider(bannersService: BannersService()), + ), + ChangeNotifierProvider( + create: (context) => + OthersCourseProvider(otherCourseService: CourseService()), + ), + ChangeNotifierProvider( + create: (context) => CartsProvider(cartService: CartService()), + ), + ChangeNotifierProvider( + create: (context) => CartProvider(cartService: CartService()), + ), + ChangeNotifierProvider( + create: (context) => WishlistProvider(), + ), + ChangeNotifierProvider( + create: (context) => PaymentsProvider( + paymentServices: PaymentServices(), + ), + ), + ChangeNotifierProvider( + create: (context) => PlayVideoCourseProvider(), + ), + ChangeNotifierProvider( + create: (context) => + MyCourseProvider(courseService: CourseService()), + ), + ChangeNotifierProvider( + create: (context) => + FilterCourseProvider(searchService: SearchService()), + ), + ChangeNotifierProvider( + create: (context) => SearchProvider(searchService: SearchService()), + ), + ChangeNotifierProvider( + create: (context) => + TopCourseProvider(courseService: CourseService()), + ), + ChangeNotifierProvider( + create: (context) => + LatestCourseProvider(courseService: CourseService()), + ), + ChangeNotifierProvider( + create: (context) => + PromoCourseProvider(courseService: CourseService()), + ), + ChangeNotifierProvider(create: (context) => OrderProvider()), + ChangeNotifierProvider(create: (context) => LoginProvider()), + ChangeNotifierProvider( + create: (context) => CertificateProvider( + certificateServices: AllCertificateServices())), + ChangeNotifierProvider(create: (context) => RadeemVoucherProvider()), + ChangeNotifierProvider( + create: (context) => IncompleteProfileProvider( + userInfoService: UserInfoService(), + )), + ChangeNotifierProvider( + create: (context) => HistoryTranscationsProvider( + historyTransactionService: HistoryTransactionService())) + ], + child: Consumer(builder: (context, themeProvider, child) { + // MaterialApp sebagai root aplikasi + return MaterialApp( + navigatorKey: navigatorKey, // Untuk navigasi global + builder: (context, widget) => ResponsiveBreakpoints.builder( + child: ClampingScrollWrapper.builder(context, widget!), + breakpoints: [ + const Breakpoint(start: 0, end: 450, name: MOBILE), + const Breakpoint(start: 451, end: 800, name: TABLET), + const Breakpoint(start: 801, end: 1920, name: DESKTOP), + const Breakpoint(start: 1921, end: double.infinity, name: '4K'), + ], + ), + debugShowCheckedModeBanner: false, + title: 'Vocasia', + theme: themeProvider.themeData, // Tema aplikasi + home: const SplashScreenLogin(), // Halaman pertama (Splash) + routes: routes, // Daftar route aplikasi + ); + }), + ), + ); + } +} diff --git a/lib/models/.gitkeep b/lib/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/models/Product.dart b/lib/models/Product.dart new file mode 100644 index 0000000..d82e7e2 --- /dev/null +++ b/lib/models/Product.dart @@ -0,0 +1,157 @@ +// import 'package:flutter/material.dart'; +// import '../size_config.dart'; +// import '../theme.dart'; + +// // class Product { +// // final int id; +// // final String title; +// // final RichText description; +// // final List images; +// // final List colors; +// // final double rating, price, disc; +// // final bool isFavourite, isPopular; + +// // Product({ +// // required this.id, +// // required this.images, +// // required this.colors, +// // this.rating = 0.0, +// // this.isFavourite = false, +// // this.isPopular = false, +// // required this.title, +// // required this.price, +// // required this.disc, +// // required this.description, +// // }); +// // } + +// // Our demo Products +// final TextStyle light = primaryTextStyle.copyWith( +// letterSpacing: 0.5, +// color: secondaryColor, +// fontSize: getProportionateScreenWidth(10), +// fontWeight: reguler); +// final TextStyle thick = primaryTextStyle.copyWith( +// letterSpacing: 0.5, +// color: secondaryColor, +// fontSize: getProportionateScreenWidth(10), +// fontWeight: semiBold); +// List demoProducts = [ +// Product( +// id: 1, +// images: [ +// "assets/images/Kur1.png", +// ], +// colors: [ +// Color(0xFFF6625E), +// Color(0xFF836DB8), +// Color(0xFFDECB9C), +// Colors.white, +// ], +// title: "Menjadi Pengusaha Sukses dan Bertalenta", +// price: 250000, +// disc: 1250000, +// description: RichText( +// text: new TextSpan( +// // Note: Styles for TextSpans must be explicitly defined. +// // Child text spans will inherit styles from parent +// style: TextStyle( +// fontSize: 10, color: Colors.white, fontWeight: FontWeight.w300), +// //maxLines: 1, +// children: [ +// new TextSpan(text: 'Oleh ', style: light), +// new TextSpan(text: 'Farid Subkhan', style: thick), +// ], +// ), +// ), +// rating: 4.8, +// isFavourite: true, +// isPopular: true, +// ), +// Product( +// id: 2, +// images: [ +// "assets/images/Kur2.png", +// ], +// colors: [ +// Color(0xFFF6625E), +// Color(0xFF836DB8), +// Color(0xFFDECB9C), +// Colors.white, +// ], +// title: "Kiat Menjadi Youtuber", +// price: 500500, +// disc: 1500000, +// description: RichText( +// text: new TextSpan( +// // Note: Styles for TextSpans must be explicitly defined. +// // Child text spans will inherit styles from parent +// style: TextStyle(fontSize: 10, fontWeight: FontWeight.w300), +// children: [ +// new TextSpan(text: 'Oleh ', style: light), +// new TextSpan(text: 'Farid Subkhan', style: thick), +// ], +// ), +// ), +// rating: 4.1, +// isPopular: true, +// ), +// Product( +// id: 3, +// images: [ +// "assets/images/Kur3.png", +// ], +// colors: [ +// Color(0xFFF6625E), +// Color(0xFF836DB8), +// Color(0xFFDECB9C), +// Colors.white, +// ], +// title: "Menjadi Pengusaha Sukses dan Berjiwa Entrepreneur", +// price: 365500, +// disc: 1500000, +// description: RichText( +// text: new TextSpan( +// // Note: Styles for TextSpans must be explicitly defined. +// // Child text spans will inherit styles from parent +// style: TextStyle(fontSize: 10, fontWeight: FontWeight.w300), +// children: [ +// new TextSpan(text: 'Oleh ', style: light), +// new TextSpan(text: 'Ali Sanjani', style: thick), +// ], +// ), +// ), +// rating: 4.1, +// isFavourite: true, +// isPopular: true, +// ), +// Product( +// id: 4, +// images: [ +// "assets/images/Kur4.png", +// ], +// colors: [ +// Color(0xFFF6625E), +// Color(0xFF836DB8), +// Color(0xFFDECB9C), +// Colors.white, +// ], +// title: "Menguasai Excel dengan Cepat dan Handal Banget", +// price: 200500, +// disc: 1000000, +// description: RichText( +// text: new TextSpan( +// // Note: Styles for TextSpans must be explicitly defined. +// // Child text spans will inherit styles from parent +// style: TextStyle(fontSize: 10, fontWeight: FontWeight.w300), +// children: [ +// new TextSpan(text: 'Oleh ', style: light), +// new TextSpan(text: 'Ali Sanjani', style: thick), +// ], +// ), +// ), +// rating: 4.1, +// isFavourite: true, +// isPopular: true, +// ), +// ]; diff --git a/lib/models/ProductProgram.dart b/lib/models/ProductProgram.dart new file mode 100644 index 0000000..e0035d1 --- /dev/null +++ b/lib/models/ProductProgram.dart @@ -0,0 +1,38 @@ +class ProductProgram { + final int id; + final List images; + + ProductProgram({ + required this.id, + required this.images, + }); +} + +// Our demo Products + +List demoProducts = [ + ProductProgram( + id: 1, + images: [ + "assets/images/workshop.png", + ], + ), + ProductProgram( + id: 2, + images: [ + "assets/images/prakerja.png", + ], + ), + ProductProgram( + id: 3, + images: [ + "assets/images/workshop.png", + ], + ), + ProductProgram( + id: 4, + images: [ + "assets/images/prakerja.png", + ], + ), +]; diff --git a/lib/models/announcement_model.dart b/lib/models/announcement_model.dart new file mode 100644 index 0000000..e9d9a10 --- /dev/null +++ b/lib/models/announcement_model.dart @@ -0,0 +1,338 @@ +import 'package:initial_folder/models/reply_announcement_model.dart'; + +class AnnouncementModel { + AnnouncementModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List> data; + + factory AnnouncementModel.fromJson(Map json) => + AnnouncementModel( + status: json["status"], + error: json["error"], + data: List>.from(json["data"].map((x) => + List.from( + x.map((x) => AnnouncementDataModel.fromJson(x))))), + ); + + Map toJson() => { + "status": status, + "error": error, + "data": List.from( + data.map((x) => List.from(x.map((x) => x.toJson())))), + }; +} + +class AnnouncementDataModel { + AnnouncementDataModel({ + this.idAnnouncement, + this.tokenAnnouncement, + this.instructorName, + this.fotoProfile, + this.bodyContent, + this.countLike, + this.likes, + this.isLike, + this.comment, + this.date, + required this.replies, + }); + + final String? idAnnouncement; + final String? tokenAnnouncement; + final String? instructorName; + final String? fotoProfile; + final String? bodyContent; + final String? countLike; + final bool? likes; + final int? isLike; + final int? comment; + final String? date; + final List replies; + + factory AnnouncementDataModel.fromJson(Map json) => + AnnouncementDataModel( + idAnnouncement: json["id_announcement"], + tokenAnnouncement: json["token_announcement"], + instructorName: json["instructor_name"], + bodyContent: json["body"], + fotoProfile: json["foto_profile"], + likes: json["likes"].length > 0, + isLike: json["is_like"], + countLike: json["count_likes"], + comment: json["comment"], + date: json["date"], + replies: List.from( + json["replies"].map((x) => ReplyModel.fromJson(x))).toList(), + ); + + Map toJson() => { + "id_announcement": idAnnouncement, + "token_announcement": tokenAnnouncement, + "instructor_name": instructorName, + "body": bodyContent, + "foto_profile": fotoProfile, + "likes": likes, + "is_like": isLike, + "comment": comment, + "date": date, + "replies": List.from(replies.map((x) => x)), + }; +} + +class AnnouncementPostModel { + AnnouncementPostModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final DataPostAnnouncement data; + + factory AnnouncementPostModel.fromJson(Map json) => + AnnouncementPostModel( + status: json["status"], + error: json["error"], + data: DataPostAnnouncement.fromJson(json["data"]), + ); + + Map toJson() => { + "status": status, + "error": error, + "data": data.toJson(), + }; +} + +class DataPostAnnouncement { + DataPostAnnouncement({this.idUser, this.idCourse, this.body}); + + final String? idUser; + final String? idCourse; + final String? body; + + factory DataPostAnnouncement.fromJson(Map json) => + DataPostAnnouncement( + idUser: json["id_user"], + idCourse: json["course_id"], + body: json["body"], + ); + + Map toJson() => { + "id_user": idUser, + "id_course": idCourse, + "body": body, + }; +} + +class AnnouncementLikeModel { + AnnouncementLikeModel({ + this.userLikes, + }); + + final String? userLikes; + + factory AnnouncementLikeModel.fromJson(Map json) => + AnnouncementLikeModel( + userLikes: json["user_likes"], + ); + + Map toJson() => { + "user_likes": userLikes, + }; +} + +class SectionModel { + SectionModel({ + this.status, + this.error, + this.progress, + required this.data, + }); + + int? status; + bool? error; + int? progress; + List> data; + + factory SectionModel.fromJson(Map json) => SectionModel( + status: json["status"], + error: json["error"], + progress: json["progress"], + data: List>.from(json["data"].map((x) => Map.from(x) + .map((k, v) => MapEntry(k, Datum.fromJson(v))))), + ); + + Map toJson() => { + "status": status, + "error": error, + "progress": progress, + "data": List.from(data.map((x) => Map.from(x) + .map((k, v) => MapEntry(k, v.toJson())))), + }; +} + +class Datum { + Datum({ + this.sectionTitle, + this.dataLesson, + }); + + String? sectionTitle; + List? dataLesson; + + factory Datum.fromJson(Map json) => Datum( + sectionTitle: json["section_title"], + dataLesson: List.from( + json["data_lesson"].map((x) => DataLesson.fromJson(x))), + ); + + Map toJson() => { + "section_title": sectionTitle, + "data_lesson": List.from(dataLesson!.map((x) => x.toJson())), + }; +} + +class DataLesson { + DataLesson({ + this.lessonId, + this.title, + this.duration, + this.attachmentType, + this.videoType, + this.videoUrl, + this.lessonType, + this.attachment, + this.isSkip, + this.isFinished, + this.summary, + }); + + String? lessonId; + String? title; + String? duration; + String? attachmentType; + String? videoType; + String? videoUrl; + String? lessonType; + dynamic attachment; + String? isSkip; + String? summary; + int? isFinished; + + factory DataLesson.fromJson(Map json) => DataLesson( + lessonId: json["lesson_id"], + title: json["title"], + duration: json["duration"], + attachmentType: json["attachment_type"], + videoType: json["video_type"], + videoUrl: json["video_url"], + lessonType: json["lesson_type"], + attachment: json["attachment"], + isSkip: json["is_skip"], + isFinished: json["is_finished"], + summary: json["summary"], + ); + + get courseId => null; + + Map toJson() => { + "lesson_id": lessonId, + "title": title, + "duration": duration, + "attachment_type": attachmentType, + "video_type": videoType, + "video_url": videoUrl, + "lesson_type": lessonType, + "attachment": attachment, + "is_skip": isSkip, + "is_finished": isFinished, + "summary": summary, + }; +} + +class NewMap { + NewMap({ + required this.title, + }); + + final List title; + + factory NewMap.fromMap(Map<String, dynamic> json) => NewMap( + title: List<Title>.from(json["Title"].map((x) => Title.fromMap(x))), + ); + + Map<String, dynamic> toMap() => { + "Title": List<dynamic>.from(title.map((x) => x.toMap())), + }; +} + +class Title { + Title({ + this.courseId, + this.lessonId, + this.sectionTitle, + this.lessonTitle, + this.duration, + this.attachmentType, + this.videoType, + this.videoUrl, + this.lessonType, + this.attachment, + this.isSkip, + this.isFinished, + this.summary, + }); + + final String? courseId; + final String? lessonId; + final String? sectionTitle; + final String? lessonTitle; + final String? duration; + final String? attachmentType; + final String? videoType; + final String? videoUrl; + final String? lessonType; + final dynamic attachment; + final String? isSkip; + final String? summary; + final int? isFinished; + + factory Title.fromMap(Map<String, dynamic> json) => Title( + courseId: json["course_id"], + lessonId: json["lesson_id"], + sectionTitle: json["section_title"], + lessonTitle: json["lesson_title"], + duration: json["duration"], + attachmentType: json["attachment_type"], + videoType: json["video_type"], + videoUrl: json["video_url"] == '' ? 'a' : json["video_url"], + lessonType: json["lesson_type"], + attachment: json["attachment"], + isSkip: json["is_skip"], + isFinished: json["is_finished"], + summary: json["summary"], + ); + + Map<String, dynamic> toMap() => { + "course_id": courseId, + "lesson_id": lessonId, + "section_title": sectionTitle, + "lesson_title": lessonTitle, + "duration": duration, + "attachment_type": attachmentType, + "video_type": videoType, + "video_url": videoUrl, + "lesson_type": lessonType, + "attachment": attachment, + "is_skip": isSkip, + "is_finished": isFinished, + "summary": summary, + }; +} diff --git a/lib/models/banners_model.dart b/lib/models/banners_model.dart new file mode 100644 index 0000000..07f7226 --- /dev/null +++ b/lib/models/banners_model.dart @@ -0,0 +1,30 @@ +class BannersModel { + BannersModel({ + this.id, + this.status, + this.img, + this.url, + this.courseId = '', + }); + String? id; + String? status; + String? img; + String? url; + String courseId = ''; + + BannersModel.fromJson(Map<String, dynamic> json) { + id = json["id"]; + status = json["status"]; + img = json["img"]; + url = json["url"]; + courseId = json["course_id"] ?? ''; + } + + Map<String, dynamic> toJson() => { + 'id': id, + 'status': status, + 'img': img, + 'url': url, + 'course_id': courseId, + }; +} diff --git a/lib/models/cart_model.dart b/lib/models/cart_model.dart new file mode 100644 index 0000000..a26e1ea --- /dev/null +++ b/lib/models/cart_model.dart @@ -0,0 +1,39 @@ +class CartModel { + CartModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final Data data; + + factory CartModel.fromJson(Map<String, dynamic> json) => CartModel( + status: json["status"], + error: json["error"], + data: Data.fromJson(json["data"]), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data.toJson(), + }; +} + +class Data { + Data({ + this.messages, + }); + + final String? messages; + + factory Data.fromJson(Map<String, dynamic> json) => Data( + messages: json["messages"], + ); + + Map<String, dynamic> toJson() => { + "messages": messages, + }; +} diff --git a/lib/models/carts_model.dart b/lib/models/carts_model.dart new file mode 100644 index 0000000..b878590 --- /dev/null +++ b/lib/models/carts_model.dart @@ -0,0 +1,165 @@ +import 'package:equatable/equatable.dart'; + +class CartsModel extends Equatable { + CartsModel({ + this.status, + this.error, + required this.data, + this.totalPayment, + this.potonganKupon, + }); + + final int? status; + final bool? error; + final List<DataCartsModel> data; + final int? potonganKupon; + final String? totalPayment; + + factory CartsModel.fromJson(Map<String, dynamic> json) => CartsModel( + status: json["status"], + error: json["error"], + data: List<DataCartsModel>.from( + json["data"].map((x) => DataCartsModel.fromJson(x))), + potonganKupon: json["potongan_kupon"], + totalPayment: json["total_payment"], + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + "total_payment": totalPayment, + }; + + @override + // TODO: implement props + List<Object?> get props => []; +} + +class DataCartsModel extends Equatable { + DataCartsModel({ + this.cartId, + this.courseId, + this.title, + this.price, + this.instructor, + this.thumbnail, + this.discountPrice, + this.discountFlag, + this.totalDiscount, + this.student, + required this.review, + this.fotoProfile, + this.coupon, + this.finalPrice, + this.potonganKupon, + }); + + final String? cartId; + final String? courseId; + final String? title; + final String? price; + final String? instructor; + final String? thumbnail; + final String? discountPrice; + final String? discountFlag; + final int? totalDiscount; + final String? student; + final List<Review> review; + final dynamic fotoProfile; + final Coupon? coupon; + final String? finalPrice; + final String? potonganKupon; + + factory DataCartsModel.fromJson(Map<String, dynamic> json) => DataCartsModel( + cartId: json["cart_id"], + courseId: json["course_id"], + title: json["title"], + price: json["price"], + instructor: json["instructor"], + thumbnail: json["thumbnail"], + discountPrice: json["discount_price"], + discountFlag: json["discount_flag"], + totalDiscount: json["total_discount"], + student: json["student"], + review: + List<Review>.from(json["review"].map((x) => Review.fromJson(x))), + fotoProfile: json["foto_profile"], + coupon: json["coupon"] == null ? null : Coupon.fromJson(json["coupon"]), + potonganKupon: json["potongan_kupon"], + finalPrice: json["final_price"], + ); + + Map<String, dynamic> toJson() => { + "cart_id": cartId, + "course_id": courseId, + "title": title, + "price": price, + "instructor": instructor, + "thumbnail": thumbnail, + "discount_price": discountPrice, + "discount_flag": discountFlag, + "total_discount": totalDiscount, + "student": student, + "review": List<dynamic>.from(review.map((x) => x.toJson())), + "foto_profile": fotoProfile, + "coupon": coupon, + "final_price": finalPrice + }; + + @override + // TODO: implement props + List<Object?> get props => [finalPrice]; +} + +class Coupon { + Coupon({ + this.id, + this.typeCoupon, + this.codeCoupon, + this.value, + this.finalPrice, + }); + + final String? id; + final String? typeCoupon; + final String? codeCoupon; + final String? value; + final int? finalPrice; + + factory Coupon.fromJson(Map<String, dynamic> json) => Coupon( + id: json["id"], + typeCoupon: json["type_coupon"], + codeCoupon: json["code_coupon"], + value: json["value"], + finalPrice: json["final_price"], + ); + + Map<String, dynamic> toJson() => { + "id": id, + "type_coupon": typeCoupon, + "code_coupon": codeCoupon, + "value": value, + "final_price": finalPrice, + }; +} + +class Review { + Review({ + this.totalReview, + this.avgRating, + }); + + final String? totalReview; + final int? avgRating; + + factory Review.fromJson(Map<String, dynamic> json) => Review( + totalReview: json["total_review"], + avgRating: json["avg_rating"] == null ? null : json["avg_rating"], + ); + + Map<String, dynamic> toJson() => { + "total_review": totalReview, + "avg_rating": avgRating == null ? null : avgRating, + }; +} diff --git a/lib/models/catagories_model.dart b/lib/models/catagories_model.dart new file mode 100644 index 0000000..78d3bee --- /dev/null +++ b/lib/models/catagories_model.dart @@ -0,0 +1,47 @@ +import 'package:initial_folder/models/subcategories_model.dart'; + +class CategoriesModel { + CategoriesModel({ + this.id, + this.nameCategory, + this.slugCategory, + this.parentCategory, + this.fontAwesomeClass, + this.subCategories, + this.subId, + }); + + final String? id; + final String? nameCategory; + final String? slugCategory; + final String? parentCategory; + final String? fontAwesomeClass; + final String? subId; + List<SubCategoryModel>? subCategories; + + factory CategoriesModel.fromJson(Map<String, dynamic> json) { + List<dynamic> subCategoriesJson = json['subcategories'] ?? []; + List<SubCategoryModel> subCategories = subCategoriesJson + .map((subCategoryJson) => SubCategoryModel.fromJson(subCategoryJson)) + .toList(); + + return CategoriesModel( + id: json["id"], + nameCategory: json["name_category"], + slugCategory: json["slug_category"], + parentCategory: json["parent_category"], + fontAwesomeClass: json["font_awesome_class"], + subId: json["sub_category_id"], + subCategories: subCategories, + ); + } + + Map<String, dynamic> toJson() => { + "id": id, + "name_category": nameCategory, + "slug_category": slugCategory, + "parent_category": parentCategory, + "font_awesome_class": fontAwesomeClass, + "sub_category_id": subId + }; +} diff --git a/lib/models/certificate_model.dart b/lib/models/certificate_model.dart new file mode 100644 index 0000000..61484ac --- /dev/null +++ b/lib/models/certificate_model.dart @@ -0,0 +1,48 @@ +class CertificateModel { + final String? idPayment; + final String? name; + final String? title; + final int? finishDate; + final String? certificateNo; + + CertificateModel({ + this.idPayment, + this.name, + this.title, + this.finishDate, + required this.certificateNo, + }); + + factory CertificateModel.fromJson(Map<String, dynamic> json) => + CertificateModel( + name: json['name'], + title: json['title'], + finishDate: json['finish_date'], + certificateNo: json['certificate_no'], + idPayment: json['id_enrol'], + ); + + Map<String, dynamic> toJson() => { + 'id_enrol': idPayment, + 'name': name, + 'title': title, + 'finishDate': finishDate, + 'certificate_no': certificateNo, + }; +} + +class CertificateNo { + CertificateNo({ + this.idPayment, + }); + + String? idPayment; + + factory CertificateNo.fromJson(Map<String, dynamic> json) => CertificateNo( + idPayment: json["id_payment"], + ); + + Map<String, dynamic> toJson() => { + "id_payment": idPayment, + }; +} diff --git a/lib/models/check_certificate.model.dart b/lib/models/check_certificate.model.dart new file mode 100644 index 0000000..ffdd603 --- /dev/null +++ b/lib/models/check_certificate.model.dart @@ -0,0 +1,74 @@ +class CheckCertificate { + int? status; + bool? error; + List<CheckCertificateData>? data; + + CheckCertificate({this.status, this.error, this.data}); + + CheckCertificate.fromJson(Map<String, dynamic> json) { + status = json['status']; + error = json['error']; + if (json['data'] != null) { + data = <CheckCertificateData>[]; + json['data'].forEach((v) { + data!.add(new CheckCertificateData.fromJson(v)); + }); + } + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['status'] = this.status; + data['error'] = this.error; + if (this.data != null) { + data['data'] = this.data!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class CheckCertificateData { + String? idPayment; + String? userId; + String? courseId; + String? name; + String? title; + dynamic finishDate; + String? certificateNo; + int? progress; + + CheckCertificateData({ + this.idPayment, + this.userId, + this.courseId, + this.name, + this.title, + this.finishDate, + this.certificateNo, + this.progress, + }); + + CheckCertificateData.fromJson(Map<String, dynamic> json) { + idPayment = json['id_enrol']; + userId = json['user_id']; + courseId = json['course_id']; + name = json['name']; + title = json['title']; + finishDate = json['finish_date']; + certificateNo = json['certificate_no']; + progress = json['progress']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['id_enrol'] = this.idPayment; + data['user_id'] = this.userId; + data['course_id'] = this.courseId; + data['name'] = this.name; + data['title'] = this.title; + data['finish_date'] = this.finishDate; + data['certificate_no'] = this.certificateNo; + data['progress'] = this.progress; + return data; + } +} diff --git a/lib/models/comment_qna_model.dart b/lib/models/comment_qna_model.dart new file mode 100644 index 0000000..06e0a0d --- /dev/null +++ b/lib/models/comment_qna_model.dart @@ -0,0 +1,51 @@ +class Comment { + Comment({ + this.idRep, + this.sender, + this.username, + this.textRep, + this.fotoProfile, + this.createAt, + }); + + String? idRep; + String? sender; + String? username; + String? textRep; + String? fotoProfile; + String? createAt; + + factory Comment.fromJson(Map<String, dynamic> json) => Comment( + idRep: json["id_rep"], + sender: json["sender"], + username: json["username"], + textRep: json["text_rep"], + fotoProfile: json["foto_profile"], + createAt: json["create_at"], + ); + + Map<String, dynamic> toJson() => { + "id_rep": idRep, + "sender": sender, + "username": username, + "text_rep": textRep, + "foto_profile": fotoProfile, + "create_at": createAt, + }; +} + +class LikeModels { + LikeModels({ + this.userId, + }); + + bool? userId; + + factory LikeModels.fromJson(Map<String, dynamic> json) => LikeModels( + userId: json["user_id"].length > 0, + ); + + Map<String, dynamic> toJson() => { + "id_rep": userId, + }; +} diff --git a/lib/models/counter_qna_comment_model.dart b/lib/models/counter_qna_comment_model.dart new file mode 100644 index 0000000..b58a1db --- /dev/null +++ b/lib/models/counter_qna_comment_model.dart @@ -0,0 +1,40 @@ +class CounterCommentModel { + CounterCommentModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + String? data; + + factory CounterCommentModel.fromJson(Map<String, dynamic> json) => + CounterCommentModel( + status: json["status"], + error: json["error"], + data: (json["data"]["count comment "]).toString(), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data, + }; +} + +// class CounterComment { +// CounterComment({ +// this.countCommentQna, +// }); + +// String? countCommentQna; + +// factory CounterComment.fromJson(Map<String, dynamic> json) => CounterComment( +// countCommentQna: json["count comment"], +// ); + +// Map<String, dynamic> toJson() => { +// "count comment": countCommentQna, +// }; +// } diff --git a/lib/models/counter_qna_like_model.dart b/lib/models/counter_qna_like_model.dart new file mode 100644 index 0000000..250bfd3 --- /dev/null +++ b/lib/models/counter_qna_like_model.dart @@ -0,0 +1,24 @@ +class CounterLikeModel { + CounterLikeModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + String? data; + + factory CounterLikeModel.fromJson(Map<String, dynamic> json) => + CounterLikeModel( + status: json["status"], + error: json["error"], + data: (json["data"]["count like "]).toString(), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data, + }; +} diff --git a/lib/models/course_model.dart b/lib/models/course_model.dart new file mode 100644 index 0000000..18195d3 --- /dev/null +++ b/lib/models/course_model.dart @@ -0,0 +1,80 @@ +import 'package:initial_folder/models/rating_course_model.dart'; + +class CourseModel { + CourseModel({ + this.idCourse = '', + this.instructorId = '', + this.title = '', + this.price = '', + this.instructorName = '', + this.discountFlag, + this.discountPrice = '', + this.promoPrice = '', + this.thumbnail, + this.students, + required this.rating, + this.totalDiscount, + this.fotoProfile, + this.topCourse, + this.isFreeCourse, + }); + + String idCourse; + String instructorId; + String title; + String price; + String instructorName; + String? discountFlag; + String discountPrice; + String promoPrice; + String? thumbnail; + String? students; + List<Rating?> rating; + int? totalDiscount; + String? fotoProfile; + String? topCourse; + String? isFreeCourse; + + factory CourseModel.fromJson(Map<String, dynamic> json) => CourseModel( + idCourse: json["id_course"] ?? '', + instructorId: json["instructor_id"] ?? '', + title: json["title"] ?? '', + price: json["price"] ?? '', + instructorName: json["instructor_name"] ?? '', + discountFlag: json["discount_flag"] ?? '', + discountPrice: json["discount_price"].toString() != "0" + ? (int.parse(json["price"]) - int.parse(json["discount_price"])) + .toString() + : json["price"] ?? '', + promoPrice: json["promo_price"].toString() != '0' + ? json["promo_price"].toString() + : json["promo_price"].toString(), + thumbnail: json["thumbnail"], + students: json["students"] ?? '', + rating: List<Rating>.from(json["rating"].map((x) => Rating.fromJson(x))) + .toList() ?? + [], + totalDiscount: json["total_discount"] ?? 0, + fotoProfile: json["foto_profile"] ?? '', + topCourse: json["top_course"] ?? '', + isFreeCourse: json["is_free_course"] ?? '', + ); + + Map<String, dynamic> toJson() => { + "id_course": idCourse, + "instructor_id": instructorId, + "title": title, + "price": price, + "instructor_name": instructorName, + "discount_flag": discountFlag, + "discount_price": discountPrice, + "promo_price": promoPrice, + "thumbnail": thumbnail, + "students": students, + "rating": List<dynamic>.from(rating.map((x) => x!.toJson())).toList(), + "total_discount": totalDiscount, + "foto_profile": fotoProfile, + "top_course": topCourse, + "is_free_course": isFreeCourse, + }; +} diff --git a/lib/models/data_diri_model.dart b/lib/models/data_diri_model.dart new file mode 100644 index 0000000..231cf28 --- /dev/null +++ b/lib/models/data_diri_model.dart @@ -0,0 +1,105 @@ +class DataDiriModel { + DataDiriModel({ + this.status, + this.error, + required this.data, + }); + + int? status; + bool? error; + List<DataOfDataDiriModel> data; + + factory DataDiriModel.fromJson(Map<String, dynamic> json) => DataDiriModel( + status: json["status"], + error: json["error"], + data: List<DataOfDataDiriModel>.from( + json["data"].map((x) => DataOfDataDiriModel.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class DataOfDataDiriModel { + DataOfDataDiriModel({ + this.idUser, + this.fullname, + this.headline, + this.biography, + this.datebirth, + this.email, + this.phone, + this.gender, + this.socialLink, + }); + + String? idUser; + String? fullname; + String? headline; + String? biography; + String? datebirth; + String? email; + String? phone; + String? gender; + SocialLink? socialLink; + + factory DataOfDataDiriModel.fromJson(Map<String, dynamic> json) => + DataOfDataDiriModel( + idUser: json["id_user"], + fullname: json["full_name"], + headline: json["headline"], + biography: json["biography"], + datebirth: json["datebirth"], + email: json["email"], + phone: json["phone"], + gender: json["jenis_kelamin"] == null + ? '' + : json["jenis_kelamin"], + socialLink: json["social_link"] == null + ? null + : SocialLink.fromJson(json["social_link"]), + ); + + Map<String, dynamic> toJson() => { + "id_user": idUser, + "full_name": fullname, + "headline": headline, + "biography": biography, + "datebirth": datebirth, + "email": email, + "phone": phone, + "jenis_kelamin": gender, + "social_link": socialLink == null ? null : socialLink!.toJson(), + }; +} + +class SocialLink { + SocialLink({ + this.facebook, + this.twitter, + this.instagram, + this.linkedin, + }); + + String? facebook; + String? twitter; + String? instagram; + String? linkedin; + + factory SocialLink.fromJson(Map<String, dynamic> json) => SocialLink( + facebook: json["facebook"], + twitter: json["twitter"], + instagram: json["instagram"], + linkedin: json["linkedin"], + ); + + Map<String, dynamic> toJson() => { + "facebook": facebook, + "twitter": twitter, + "instagram": instagram, + "linkedin": linkedin, + }; +} diff --git a/lib/models/detail_course_coupon_model.dart b/lib/models/detail_course_coupon_model.dart new file mode 100644 index 0000000..5d7ceb1 --- /dev/null +++ b/lib/models/detail_course_coupon_model.dart @@ -0,0 +1,85 @@ +class DetailCourseCoupon { + int? status; + bool? error; + List<DataDetailCourseCoupon>? data; + + DetailCourseCoupon({this.status, this.error, this.data}); + + DetailCourseCoupon.fromJson(Map<String, dynamic> json) { + status = json['status']; + error = json['error']; + if (json['data'] != null) { + data = <DataDetailCourseCoupon>[]; + json['data'].forEach((v) { + data!.add(new DataDetailCourseCoupon.fromJson(v)); + }); + } + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['status'] = this.status; + data['error'] = this.error; + if (this.data != null) { + data['data'] = this.data!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class DataDetailCourseCoupon { + String? idCourse; + String? typeCoupon; + String? value; + String? discountFlag; + String? courseName; + String? instructor; + String? fotoProfile; + String? thubmnail; + String? originalPrice; + int? discountPrice; + int? finalPrice; + + DataDetailCourseCoupon( + {this.idCourse, + this.typeCoupon, + this.value, + this.discountFlag, + this.courseName, + this.instructor, + this.fotoProfile, + this.thubmnail, + this.originalPrice, + this.discountPrice, + this.finalPrice}); + + DataDetailCourseCoupon.fromJson(Map<String, dynamic> json) { + idCourse = json['id_course']; + typeCoupon = json['type_coupon']; + value = json['value']; + discountFlag = json['discount_flag']; + courseName = json['course_name']; + instructor = json['instructor']; + fotoProfile = json['foto_profile']; + thubmnail = json['thubmnail']; + originalPrice = json['original_price']; + discountPrice = json['discount_price']; + finalPrice = json['final_price']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['id_course'] = this.idCourse; + data['type_coupon'] = this.typeCoupon; + data['value'] = this.value; + data['discount_flag'] = this.discountFlag; + data['course_name'] = this.courseName; + data['instructor'] = this.instructor; + data['foto_profile'] = this.fotoProfile; + data['thubmnail'] = this.thubmnail; + data['original_price'] = this.originalPrice; + data['discount_price'] = this.discountPrice; + data['final_price'] = this.finalPrice; + return data; + } +} diff --git a/lib/models/detail_course_model.dart b/lib/models/detail_course_model.dart new file mode 100644 index 0000000..8711772 --- /dev/null +++ b/lib/models/detail_course_model.dart @@ -0,0 +1,219 @@ +import 'package:initial_folder/models/rating_course_model.dart'; +// To parse this JSON data, do +// +// final detailCourseModel = detailCourseModelFromJson(jsonString); + +import 'dart:convert'; + +DetailCourseModel detailCourseModelFromJson(String str) => + DetailCourseModel.fromJson(json.decode(str)); + +String detailCourseModelToJson(DetailCourseModel data) => + json.encode(data.toJson()); + +class DetailCourseModel { + DetailCourseModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List<List<DataDetailCourseModel>> data; + + factory DetailCourseModel.fromJson(Map<String, dynamic> json) => + DetailCourseModel( + status: json["status"], + error: json["error"], + data: List<List<DataDetailCourseModel>>.from(json["data"].map((x) => + List<DataDetailCourseModel>.from( + x.map((x) => DataDetailCourseModel.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from( + data.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class DataDetailCourseModel { + DataDetailCourseModel( + {required this.id, + this.title, + this.instructorId, + this.instructor, + this.shortDescription, + this.levelCourse, + this.totalLesson, + this.totalStudents, + this.description, + this.outcome, + this.requirement, + this.price, + this.discountPrice, + this.discountFlag, + this.videoUrl, + this.totalDuration, + this.bio, + required this.rating, + this.totalDiscount, + this.lastModified, + this.fotoProfile, + this.thumbnail, + this.isFreeCourse, + this.isMine, + required this.breadcrumbs, + this.headlineInstructor, + this.status_course, + this.checkoutPrice, + this.typeCoupon, + this.value, + this.courseName, + this.originalPrice, + this.finalPrice, + this.promoPrice}); + + final String id; + final String? title; + final String? instructorId; + final String? instructor; + final String? shortDescription; + final String? levelCourse; + final String? totalLesson; + final String? totalStudents; + final String? description; + final String? outcome; + final String? requirement; + final String? price; + final String? discountPrice; + final String? discountFlag; + final String? videoUrl; + final String? totalDuration; + final String? bio; + final List<Rating> rating; + final int? totalDiscount; + final String? lastModified; + final dynamic fotoProfile; + final String? thumbnail; + final String? isFreeCourse; + final dynamic isMine; + final Breadcrumbs breadcrumbs; + final String? status_course; + final String? headlineInstructor; + final String? typeCoupon; + final String? value; + final String? courseName; + final String? originalPrice; + final String? finalPrice; + final int? checkoutPrice; + final String? promoPrice; + + factory DataDetailCourseModel.fromJson(Map<String, dynamic> json) => + DataDetailCourseModel( + id: json["id"], + title: json["title"], + instructorId: json["instructor_id"], + instructor: json["instructor"], + shortDescription: json["short_description"], + levelCourse: json["level_course"], + totalLesson: json["total_lesson"], + totalStudents: json["total_students"], + description: json["description"] == null ? '' : json["description"], + outcome: json["outcome"] == "[]" ? '' : json["outcome"], + requirement: json["requirement"], + price: json["price"].toString(), + discountPrice: json["discount_price"].toString() != "0" + ? json["discount_price"].toString() + : "0", + discountFlag: json["discount_flag"], + videoUrl: json["video_url"], + totalDuration: json["total_duration"], + bio: json["bio"], + rating: + List<Rating>.from(json["rating"].map((x) => Rating.fromJson(x))), + totalDiscount: json["total_discount"], + lastModified: json["last_modified"], + fotoProfile: json["foto_profile"], + thumbnail: json["thumbnail"], + isFreeCourse: json["is_free_course"], + isMine: json["is_mine"], + breadcrumbs: Breadcrumbs.fromJson(json["breadcrumbs"]), + headlineInstructor: json['headline'], + status_course: json["status_course"], + checkoutPrice: json["checkout_price"], + typeCoupon: json["type_coupon"], + value: json["value"], + courseName: json["course_name"], + originalPrice: json["original_price"], + finalPrice: json["final_price"], + promoPrice: json["promo_price"].toString() != '0' + ? json["promo_price"].toString() + : json["promo_price"].toString(), + ); + + Map<String, dynamic> toJson() => { + "id": id, + "title": title, + "instructor_id": instructorId, + "instructor": instructor, + "short_description": shortDescription, + "level_course": levelCourse, + "total_lesson": totalLesson, + "total_students": totalStudents, + "description": description, + "outcome": outcome, + "requirement": requirement, + "price": price, + "discount_price": discountPrice, + "discount_flag": discountFlag, + "video_url": videoUrl, + "total_duration": totalDuration, + "bio": bio, + "rating": List<dynamic>.from(rating.map((x) => x.toJson())), + "total_discount": totalDiscount, + "last_modified": lastModified, + "foto_profile": fotoProfile, + "thumbnail": thumbnail, + "is_free_course": isFreeCourse, + "is_mine": isMine, + "breadcrumbs": breadcrumbs.toJson(), + "checkout_price": checkoutPrice, + "type_coupon": typeCoupon, + "value": value, + "course_name": courseName, + "original_price": originalPrice, + "final_price": finalPrice, + "promo_price": promoPrice, + }; +} + +class Breadcrumbs { + Breadcrumbs({ + this.idCategory, + this.parentName, + this.subCategoryId, + this.subParentCategory, + }); + + final String? idCategory; + final String? parentName; + final String? subCategoryId; + final String? subParentCategory; + + factory Breadcrumbs.fromJson(Map<String, dynamic> json) => Breadcrumbs( + idCategory: json["id_category"], + parentName: json["parent_name"], + subCategoryId: json["sub_category_id"], + subParentCategory: json["sub_parent_category"], + ); + + Map<String, dynamic> toJson() => { + "id_category": idCategory, + "parent_name": parentName, + "sub_category_id": subCategoryId, + "sub_parent_category": subParentCategory, + }; +} diff --git a/lib/models/detail_invoice_model.dart b/lib/models/detail_invoice_model.dart new file mode 100644 index 0000000..c85ed71 --- /dev/null +++ b/lib/models/detail_invoice_model.dart @@ -0,0 +1,168 @@ +class DetailInvoiceModel { + int? status; + bool? error; + List<DataDetailInvoiceModel>? data; + + DetailInvoiceModel({this.status, this.error, this.data}); + + DetailInvoiceModel.fromJson(Map<String, dynamic> json) { + status = json['status']; + error = json['error']; + if (json['data'] != null) { + data = <DataDetailInvoiceModel>[]; + json['data'].forEach((v) { + data!.add(new DataDetailInvoiceModel.fromJson(v)); + }); + } + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['status'] = this.status; + data['error'] = this.error; + if (this.data != null) { + data['data'] = this.data!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class DataDetailInvoiceModel { + String? statusCode; + String? transactionId; + String? grossAmount; + String? currency; + String? orderId; + String? paymentType; + String? signatureKey; + String? transactionStatus; + String? fraudStatus; + String? statusMessage; + String? merchantId; + String? permataVaNumber; + String? transactionTime; + String? expiryTime; + String? billerCode; + String? billKey; + String? store; + String? paymentCode; + String? bank; + String? maskedCard; + List<VaNumbersModel>? vaNumbers; + + DataDetailInvoiceModel({ + this.statusCode, + this.transactionId, + this.grossAmount, + this.currency, + this.orderId, + this.paymentType, + this.signatureKey, + this.transactionStatus, + this.fraudStatus, + this.statusMessage, + this.merchantId, + this.permataVaNumber, + this.transactionTime, + this.expiryTime, + this.billerCode, + this.billKey, + this.vaNumbers, + this.store, + this.paymentCode, + this.bank, + this.maskedCard, + }); + + DataDetailInvoiceModel.fromJson(Map<String, dynamic> json) { + statusCode = json['status_code']; + transactionId = json['transaction_id']; + grossAmount = json['gross_amount']; + currency = json['currency']; + orderId = json['order_id']; + paymentType = json['payment_type']; + bank = json['bank']; + signatureKey = json['signature_key']; + transactionStatus = json['transaction_status']; + fraudStatus = json['fraud_status']; + statusMessage = json['status_message']; + merchantId = json['merchant_id']; + permataVaNumber = json['permata_va_number']; + transactionTime = json['transaction_time']; + expiryTime = json['expiry_time']; + billerCode = json['biller_code']; + billKey = json['bill_key']; + store = json['store']; + paymentCode = json['payment_code']; + maskedCard = json['masked_card']; + if (json['va_numbers'] != null) { + vaNumbers = <VaNumbersModel>[]; + json['va_numbers'].forEach((v) { + vaNumbers!.add(new VaNumbersModel.fromJson(v)); + }); + } + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['status_code'] = this.statusCode; + data['transaction_id'] = this.transactionId; + data['gross_amount'] = this.grossAmount; + data['currency'] = this.currency; + data['order_id'] = this.orderId; + data['payment_type'] = this.paymentType; + data['signature_key'] = this.signatureKey; + data['transaction_status'] = this.transactionStatus; + data['fraud_status'] = this.fraudStatus; + data['status_message'] = this.statusMessage; + data['merchant_id'] = this.merchantId; + data['permata_va_number'] = this.permataVaNumber; + data['transaction_time'] = this.transactionTime; + data['expiry_time'] = this.expiryTime; + data['biller_code'] = this.billerCode; + data['bill_key'] = this.billKey; + data['store'] = this.store; + data['payment_code'] = this.paymentCode; + data['bank'] = this.bank; + data['masked_card'] = this.maskedCard; + if (this.vaNumbers != null) { + data['va_numbers'] = this.vaNumbers!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class VaNumbersModel { + String? bank; + String? vaNumber; + + VaNumbersModel({this.bank, this.vaNumber}); + + VaNumbersModel.fromJson(Map<String, dynamic> json) { + bank = json['bank']; + vaNumber = json['va_number']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['bank'] = this.bank; + data['va_number'] = this.vaNumber; + return data; + } +} + +class StoreModel { + String? store; + + StoreModel({this.store}); + + StoreModel.fromJson(Map<String, dynamic> json) { + store = json['store']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['store'] = this.store; + return data; + } +} diff --git a/lib/models/detail_order_model.dart b/lib/models/detail_order_model.dart new file mode 100644 index 0000000..254de1f --- /dev/null +++ b/lib/models/detail_order_model.dart @@ -0,0 +1,172 @@ +class DetailOrderModel { + String idOrder; + List<VirtualNumbers>? bankVa; + String? virtualNumber; + String? billerCode; + String? bankName; + String paymentType; + String? email; + String? name; + String? url; + // List<OrderModel>? orders; + String totalPayment; + DateTime transactionTime; + DateTime transactionTimeLimit; + String? qrCodeUrl; + String? urlGopay; + String? transactionStatus; + String? merchantId; + String? coupon; + + DetailOrderModel({ + required this.idOrder, + this.bankVa, + this.url, + this.bankName, + this.qrCodeUrl, + this.urlGopay, + this.email, + this.name, + this.virtualNumber, + this.billerCode, + this.transactionStatus, + this.merchantId, + this.coupon, + // this.orders, + required this.transactionTime, + required this.transactionTimeLimit, + required this.totalPayment, + required this.paymentType, + }); + + factory DetailOrderModel.fromJson(Map<String, dynamic> json) { + final pType = json['payment_type'] ?? ''; + final permataCheck = json['status_message'] ?? ''; + final total = json['gross_amount'] ?? ''; + final statusTransaction = json['transaction_status'] ?? ''; + final time = DateTime.parse(json['transaction_time'] ?? ''); + + if (pType == 'echannel') { + // Model for Payment Mandiri VA + return DetailOrderModel( + transactionTime: time, + idOrder: json["order_id"] ?? '', + virtualNumber: json["bill_key"] ?? '', + billerCode: json["biller_code"] ?? '', + bankName: 'mandiri', + paymentType: json['payment_type'] ?? '', + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1))); + } else if (pType == 'bank_transfer' && + permataCheck == 'Success, PERMATA VA transaction is successful') { + // Model for Payment Permata VA + return DetailOrderModel( + transactionTime: time, + idOrder: json["order_id"] ?? '', + virtualNumber: json["permata_va_number"] ?? '', + bankName: 'permata', + paymentType: json['payment_type'] ?? '', + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1))); + } else if (pType == 'bank_transfer') { + // Model for Payment Bank Transfer (BCA & BNI) + final bankVAResponse = List<VirtualNumbers>.from( + json["va_numbers"].map((x) => VirtualNumbers.fromJson(x))).toList(); + return DetailOrderModel( + transactionTime: time, + idOrder: json['order_id'] ?? '', + paymentType: pType, + bankVa: bankVAResponse, + virtualNumber: bankVAResponse[0].vaNumber, + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1)), + bankName: bankVAResponse[0].bank); + } else if (pType == 'credit_card') { + // Model for CC Payment + return DetailOrderModel( + transactionTime: time, + url: json['redirect_url'] ?? '', + idOrder: json['order_id'] ?? '', + paymentType: pType, + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1)), + bankName: json['bank'] ?? ''); + } else if (pType == 'cstore') { + // Model for Store (Indomaret & Alfamart) + return DetailOrderModel( + transactionTime: time, + idOrder: json["order_id"] ?? '', + virtualNumber: json["payment_code"] ?? '', + merchantId: json["merchant_id"] ?? '', + paymentType: pType, + bankName: json['store'] ?? '', + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1))); + } else { + // Model for GoPay + final qrUrl = json['actions'][0]['url']; + final urlGop = json['actions'][1]['url']; + + return DetailOrderModel( + idOrder: json['order_id'] ?? '', + transactionTime: time, + transactionTimeLimit: time.add(Duration(days: 1)), + totalPayment: total, + paymentType: pType, + transactionStatus: statusTransaction, + qrCodeUrl: qrUrl, + urlGopay: urlGop, + ); + } + } + + Map<String, dynamic> toJson() { + return paymentType == "echannel" + ? { + "order_id": idOrder, + "payment_type": paymentType, + "bill_key": virtualNumber, + "biller_code": billerCode, + "transaction_status": transactionStatus, + } + : paymentType == 'permata' + ? { + "order_id": idOrder, + "payment_type": paymentType, + "permata_va_number": bankVa, + "transaction_status": transactionStatus, + } + : { + "order_id": idOrder, + "va_numbers": + List<dynamic>.from(bankVa!.map((x) => x.toJson())).toList(), + "payment_type": paymentType, + "transaction_status": transactionStatus, + }; + } +} + +class VirtualNumbers { + VirtualNumbers({ + this.bank, + this.vaNumber, + }); + + String? bank; + String? vaNumber; + + factory VirtualNumbers.fromJson(Map<String, dynamic> json) => VirtualNumbers( + bank: json["bank"], + vaNumber: json["va_number"], + ); + + Map<String, dynamic> toJson() => { + "bank": bank, + "va_number": vaNumber, + }; +} diff --git a/lib/models/detail_order_model_underscore.dart b/lib/models/detail_order_model_underscore.dart new file mode 100644 index 0000000..1002266 --- /dev/null +++ b/lib/models/detail_order_model_underscore.dart @@ -0,0 +1,169 @@ +class DetailOrderModelUnderscore { + String idOrder; + List<VirtualNumbers>? bankVa; + String? virtualNumber; + String? billerCode; + String? bankName; + String paymentType; + String? email; + String? name; + String? url; + // List<OrderModel>? orders; + String totalPayment; + DateTime transactionTime; + DateTime transactionTimeLimit; + String? qrCodeUrl; + String? urlGopay; + String? transactionStatus; + String? merchantId; + + DetailOrderModelUnderscore({ + required this.idOrder, + this.bankVa, + this.url, + this.bankName, + this.qrCodeUrl, + this.urlGopay, + this.email, + this.name, + this.virtualNumber, + this.billerCode, + this.transactionStatus, + this.merchantId, + // this.orders, + required this.transactionTime, + required this.transactionTimeLimit, + required this.totalPayment, + required this.paymentType, + }); + + factory DetailOrderModelUnderscore.fromJson(Map<String, dynamic> json) { + final pType = json['payment_type'] ?? ''; + final permataCheck = json['status_message'] ?? ''; + final total = json['gross_amount'] ?? ''; + final statusTransaction = json['transaction_status'] ?? ''; + final time = DateTime.parse(json['transaction_time'] ?? ''); + + if (pType == 'echannel') { + // Model for Payment Mandiri VA + return DetailOrderModelUnderscore( + transactionTime: time, + idOrder: json["order_id"] ?? '', + virtualNumber: json["bill_key"] ?? '', + billerCode: json["biller_code"] ?? '', + bankName: 'mandiri', + paymentType: json['payment_type'] ?? '', + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1))); + } else if (pType == 'bank_transfer' && + permataCheck == 'Success, PERMATA VA transaction is successful') { + // Model for Payment Permata VA + return DetailOrderModelUnderscore( + transactionTime: time, + idOrder: json["order_id"] ?? '', + virtualNumber: json["permata_va_number"] ?? '', + bankName: 'permata', + paymentType: json['payment_type'] ?? '', + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1))); + } else if (pType == 'bank_transfer') { + // Model for Payment Bank Transfer (BCA & BNI) + final bankVAResponse = List<VirtualNumbers>.from( + json["va_numbers"].map((x) => VirtualNumbers.fromJson(x))).toList(); + return DetailOrderModelUnderscore( + transactionTime: time, + idOrder: json['order_id'] ?? '', + paymentType: pType, + bankVa: bankVAResponse, + virtualNumber: bankVAResponse[0].vaNumber, + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1)), + bankName: bankVAResponse[0].bank); + } else if (pType == 'credit_card') { + // Model for CC Payment + return DetailOrderModelUnderscore( + transactionTime: time, + url: json['redirect_url'] ?? '', + idOrder: json['order_id'] ?? '', + paymentType: pType, + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1)), + bankName: json['bank'] ?? ''); + } else if (pType == 'cstore') { + // Model for Store (Indomaret & Alfamart) + return DetailOrderModelUnderscore( + transactionTime: time, + idOrder: json["order_id"] ?? '', + virtualNumber: json["payment_code"] ?? '', + merchantId: json["merchant_id"] ?? '', + paymentType: pType, + bankName: json['store'] ?? '', + totalPayment: total, + transactionStatus: statusTransaction, + transactionTimeLimit: time.add(Duration(days: 1))); + } else { + // Model for GoPay + final qrUrl = json['actions'][0]['url']; + final urlGop = json['actions'][1]['url']; + + return DetailOrderModelUnderscore( + idOrder: json['order_id'] ?? '', + transactionTime: time, + transactionTimeLimit: time.add(Duration(days: 1)), + totalPayment: total, + paymentType: pType, + transactionStatus: statusTransaction, + qrCodeUrl: qrUrl, + urlGopay: urlGop, + ); + } + } + + Map<String, dynamic> toJson() { + return paymentType == "echannel" + ? { + "order_id": idOrder, + "payment_type": paymentType, + "bill_key": bankVa, + "transaction_status": transactionStatus, + } + : paymentType == 'permata' + ? { + "order_id": idOrder, + "payment_type": paymentType, + "permata_va_number": bankVa, + "transaction_status": transactionStatus, + } + : { + "order_id": idOrder, + "va_numbers": + List<dynamic>.from(bankVa!.map((x) => x.toJson())).toList(), + "payment_type": paymentType, + "transaction_status": transactionStatus, + }; + } +} + +class VirtualNumbers { + VirtualNumbers({ + this.bank, + this.vaNumber, + }); + + String? bank; + String? vaNumber; + + factory VirtualNumbers.fromJson(Map<String, dynamic> json) => VirtualNumbers( + bank: json["bank"], + vaNumber: json["va_number"], + ); + + Map<String, dynamic> toJson() => { + "bank": bank, + "va_number": vaNumber, + }; +} diff --git a/lib/models/detail_rating_course_model.dart b/lib/models/detail_rating_course_model.dart new file mode 100644 index 0000000..86a1484 --- /dev/null +++ b/lib/models/detail_rating_course_model.dart @@ -0,0 +1,110 @@ +class RatingCourseDetailModel { + RatingCourseDetailModel({ + this.status, + this.error, + required this.data, + required this.dataReview, + }); + + final int? status; + final bool? error; + final Data data; + final List<DataReview> dataReview; + + factory RatingCourseDetailModel.fromJson(Map<String, dynamic> json) => + RatingCourseDetailModel( + status: json["status"], + error: json["error"], + data: Data.fromJson(json["data"]), + dataReview: List<DataReview>.from( + json["data_review"].map((x) => DataReview.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data.toJson(), + "data_review": List<dynamic>.from(dataReview.map((x) => x.toJson())), + }; +} + +class Data { + Data({ + this.avgRating, + required this.precentageRating, + }); + + final dynamic avgRating; + final PrecentageRating precentageRating; + + factory Data.fromJson(Map<String, dynamic> json) => Data( + avgRating: json["avg_rating"], + precentageRating: PrecentageRating.fromJson(json["precentage_rating"]), + ); + + Map<String, dynamic> toJson() => { + "avg_rating": avgRating, + "precentage_rating": precentageRating.toJson(), + }; +} + +class PrecentageRating { + PrecentageRating({ + this.rating1, + this.rating2, + this.rating3, + this.rating4, + this.rating5, + }); + + final dynamic rating1; + final dynamic rating2; + final dynamic rating3; + final dynamic rating4; + final dynamic rating5; + + factory PrecentageRating.fromJson(Map<String, dynamic> json) => + PrecentageRating( + rating1: json["rating_1"], + rating2: json["rating_2"], + rating3: json["rating_3"], + rating4: json["rating_4"], + rating5: json["rating_5"], + ); + + Map<String, dynamic> toJson() => { + "rating_1": rating1, + "rating_2": rating2, + "rating_3": rating3, + "rating_4": rating4, + "rating_5": rating5, + }; +} + +class DataReview { + DataReview({ + this.name, + this.review, + this.rating, + this.date, + }); + + final String? name; + final String? review; + final String? rating; + final String? date; + + factory DataReview.fromJson(Map<String, dynamic> json) => DataReview( + name: json["name"], + review: json["review"], + rating: json["rating"], + date: json["date"], + ); + + Map<String, dynamic> toJson() => { + "name": name, + "review": review, + "rating": rating, + "date": date, + }; +} diff --git a/lib/models/discount_course_model.dart b/lib/models/discount_course_model.dart new file mode 100644 index 0000000..0aa8323 --- /dev/null +++ b/lib/models/discount_course_model.dart @@ -0,0 +1,91 @@ +import 'package:initial_folder/models/rating_course_model.dart'; + +class DiscountCourseModel { + DiscountCourseModel({ + this.idCourse = '', + this.instructorId = '', + this.title = '', + this.price = '', + this.instructorName = '', + this.discountFlag, + this.discountPrice = '', + this.thumbnail, + this.students, + required this.rating, + this.finalPrice, + this.fotoProfile, + this.topCourse, + this.hargaTotalDiscount, + this.typeCoupon, + this.value, + }); + + String idCourse; + String instructorId; + String title; + String price; + String instructorName; + String? discountFlag; + String discountPrice; + String? thumbnail; + String? students; + List<Rating?> rating; + int? finalPrice; + int? hargaTotalDiscount; + String? fotoProfile; + String? topCourse; + String? typeCoupon; + String? value; + + factory DiscountCourseModel.fromJson(Map<String, dynamic> json) { + int parseIntWithDotRemoval(dynamic value) { + if (value is int) return value; + return int.tryParse(value.toString().replaceAll('.', '')) ?? 0; + } + + return DiscountCourseModel( + idCourse: json["id_course"].toString(), + instructorId: json["instructor_id"].toString(), + title: json["title"].toString(), + price: json["price"].toString(), + instructorName: json["instructor_name"].toString(), + discountFlag: json["discount_flag"]?.toString(), + discountPrice: json["discount_price"].toString(), + thumbnail: json["thumbnail"]?.toString(), + students: json["students"]?.toString(), + rating: List<Rating>.from(json["rating"].map((x) => Rating.fromJson(x))) + .toList(), + finalPrice: parseIntWithDotRemoval(json["final_price"]), + // finalPrice: json["final_price"] is int + // ? json["final_price"] + // : int.tryParse(json["final_price"].toString()), + fotoProfile: json["foto_profile"]?.toString(), + topCourse: json["top_course"]?.toString(), + typeCoupon: json["type_coupon"]?.toString(), + value: json["value"]?.toString(), + hargaTotalDiscount: parseIntWithDotRemoval(json["harga_total_discount"]), + // hargaTotalDiscount: json["harga_total_discount"] is int + // ? json["harga_total_discount"] + // : int.tryParse(json["harga_total_discount"].toString()), + ); + } + + Map<String, dynamic> toJson() => { + "id_course": idCourse, + "instructor_id": instructorId, + "title": title, + "price": price, + "instructor_name": instructorName, + "discount_flag": discountFlag, + "discount_price": discountPrice, + "thumbnail": thumbnail, + "students": students, + "rating": List<dynamic>.from(rating.map((x) => x!.toJson())).toList(), + "final_price": finalPrice, + "foto_profile": fotoProfile, + "top_course": topCourse, + "harga_total_discount": hargaTotalDiscount, + "type_coupon": typeCoupon, + "value": value, + }; +} diff --git a/lib/models/forgot_password_model.dart b/lib/models/forgot_password_model.dart new file mode 100644 index 0000000..6f69ef4 --- /dev/null +++ b/lib/models/forgot_password_model.dart @@ -0,0 +1,15 @@ +class ForgotPasswordModel { + String? email; + + ForgotPasswordModel({this.email}); + + ForgotPasswordModel.fromJson(Map<String, dynamic> json) { + email = json['email']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['email'] = this.email; + return data; + } +} diff --git a/lib/models/history_transaction_model.dart b/lib/models/history_transaction_model.dart new file mode 100644 index 0000000..dac2434 --- /dev/null +++ b/lib/models/history_transaction_model.dart @@ -0,0 +1,100 @@ +class HistoryTransactionModel { + HistoryTransactionModel({ + this.orderId, + this.totalPrice, + this.date, + this.dateExpired, + this.paymentDetail, + this.statusPayment, + this.courses, + this.discountPrice, + this.token, + }); + + final String? orderId; + final DateTime? date; + final DateTime? dateExpired; + final List<DataCourses>? courses; + final int? totalPrice; + final int? discountPrice; + final PaymentDetail? paymentDetail; + final String? statusPayment; + final String? token; + + factory HistoryTransactionModel.fromJson(Map<String, dynamic> json) { + int timestamp = int.parse(json["date"]); + int timestampExpired = int.parse(json["date_expired"]); + return HistoryTransactionModel( + orderId: json['order_id'] ?? '', + totalPrice: json["sub_total"] ?? 0, + discountPrice: json["discount_price"] ?? 0, + date: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), + dateExpired: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), + paymentDetail: PaymentDetail.fromJson(json["payment_detail"] ?? {}), + statusPayment: json["status"] ?? '', + token: json['token'] ?? '', + courses: json["course"] != null + ? List<DataCourses>.from( + json["course"].map((x) => DataCourses.fromJson(x))).toList() + : []); + } + + Map<dynamic, dynamic> toJson() => { + 'order_id': orderId, + "sub_total": totalPrice, + "discountPrice": discountPrice, + "payment_type": paymentDetail, + "status": statusPayment, + "token": token, + 'course': courses != null + ? List<dynamic>.from(courses!.map((x) => x.toJson())).toList() + : [] + }; +} + +class DataCourses { + final String? courseId, thumbnail, title, instructor, price; + + DataCourses( + {this.courseId, this.title, this.price, this.instructor, this.thumbnail}); + + factory DataCourses.fromJson(Map<String, dynamic> json) => DataCourses( + courseId: json['id'] ?? '', + title: json['title'] ?? '', + price: json['price'] ?? '', + thumbnail: json['thumbnail'] ?? '', + instructor: json['instructor'] ?? '', + ); + + Map<String, dynamic> toJson() => { + 'id': courseId, + 'title': title, + 'price': price, + 'thumbnail': thumbnail, + 'instructor': instructor + }; +} + +class PaymentDetail { + String? paymentType; + String? bank; + String? vaNumber; + String? store; + + PaymentDetail({this.paymentType, this.bank, this.vaNumber, this.store}); + + PaymentDetail.fromJson(Map<String, dynamic> json) { + paymentType = json['payment_type'] ?? ''; + bank = json['bank'] ?? ''; + vaNumber = json['va_number'] ?? ''; + store = json['store'] ?? ''; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['payment_type'] = this.paymentType; + data['bank'] = this.bank; + data['va_number'] = this.vaNumber; + return data; + } +} diff --git a/lib/models/instructor_model.dart b/lib/models/instructor_model.dart new file mode 100644 index 0000000..5b52dfd --- /dev/null +++ b/lib/models/instructor_model.dart @@ -0,0 +1,53 @@ +class InstructorModel { + InstructorModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List<DataInstructor> data; + + factory InstructorModel.fromJson(Map<String, dynamic> json) => + InstructorModel( + status: json["status"], + error: json["error"], + data: List<DataInstructor>.from( + json["data"].map((x) => DataInstructor.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class DataInstructor { + DataInstructor({ + this.instructorName, + this.totalReview, + this.totalCourse, + this.totalStudents, + }); + + final String? instructorName; + final String? totalReview; + final String? totalCourse; + final String? totalStudents; + + factory DataInstructor.fromJson(Map<String, dynamic> json) => DataInstructor( + instructorName: json["instructor_name"], + totalReview: json["total_review"], + totalCourse: json["total_course"], + totalStudents: json["total_students"], + ); + + Map<String, dynamic> toJson() => { + "instructor_name": instructorName, + "total_review": totalReview, + "total_course": totalCourse, + "total_students": totalStudents, + }; +} diff --git a/lib/models/lesson_course_model.dart b/lib/models/lesson_course_model.dart new file mode 100644 index 0000000..b6ac70d --- /dev/null +++ b/lib/models/lesson_course_model.dart @@ -0,0 +1,164 @@ +class LessonCourseModel { + LessonCourseModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List<List<DataLessonCourseModel>> data; + + factory LessonCourseModel.fromJson(Map<String, dynamic> json) => + LessonCourseModel( + status: json["status"], + error: json["error"], + data: List<List<DataLessonCourseModel>>.from(json["data"].map((x) => + List<DataLessonCourseModel>.from( + x.map((x) => DataLessonCourseModel.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from( + data.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class DataLessonCourseModel { + DataLessonCourseModel({ + this.courseId, + this.lessonId, + this.sectionTitle, + this.lessonTitle, + this.duration, + this.attachmentType, + this.videoType, + this.videoUrl, + this.lessonType, + this.attachment, + this.isSkip, + this.isFinished, + }); + + final String? courseId; + final String? lessonId; + final String? sectionTitle; + final String? lessonTitle; + final String? duration; + final String? attachmentType; + final String? videoType; + final String? videoUrl; + final String? lessonType; + final dynamic attachment; + final String? isSkip; + late int? isFinished; + + factory DataLessonCourseModel.fromJson(Map<String, dynamic> json) => + DataLessonCourseModel( + courseId: json["course_id"], + lessonId: json["lesson_id"], + sectionTitle: json["section_title"], + lessonTitle: json["lesson_title"], + duration: json["duration"], + attachmentType: json["attachment_type"], + videoType: json["video_type"] == null ? null : json["video_type"], + videoUrl: json["video_url"] == null ? null : json["video_url"], + lessonType: json["lesson_type"], + attachment: json["attachment"], + isSkip: json["is_skip"], + isFinished: json["is_finished"], + ); + + Map<String, dynamic> toJson() => { + "course_id": courseId, + "lesson_id": lessonId, + "section_title": sectionTitle, + "lesson_title": lessonTitle, + "duration": duration, + "attachment_type": attachmentType, + "video_type": videoType == null ? null : videoType, + "video_url": videoUrl == null ? null : videoUrl, + "lesson_type": lessonType, + "attachment": attachment, + "is_skip": isSkip, + "is_finished": isFinished, + }; +} + +class NewMap { + NewMap({ + required this.title, + }); + + final List<Title> title; + + factory NewMap.fromMap(Map<String, dynamic> json) => NewMap( + title: List<Title>.from(json["Title"].map((x) => Title.fromMap(x))), + ); + + Map<String, dynamic> toMap() => { + "Title": List<dynamic>.from(title.map((x) => x.toMap())), + }; +} + +class Title { + Title({ + this.courseId, + this.lessonId, + this.sectionTitle, + this.lessonTitle, + this.duration, + this.attachmentType, + this.videoType, + this.videoUrl, + this.lessonType, + this.attachment, + this.isSkip, + this.isFinished, + }); + + final String? courseId; + final String? lessonId; + final String? sectionTitle; + final String? lessonTitle; + final String? duration; + final String? attachmentType; + final String? videoType; + final String? videoUrl; + final String? lessonType; + final dynamic attachment; + final String? isSkip; + final int? isFinished; + + factory Title.fromMap(Map<String, dynamic> json) => Title( + courseId: json["course_id"], + lessonId: json["lesson_id"], + sectionTitle: json["section_title"], + lessonTitle: json["lesson_title"], + duration: json["duration"], + attachmentType: json["attachment_type"], + videoType: json["video_type"], + videoUrl: json["video_url"] == '' ? 'a' : json["video_url"], + lessonType: json["lesson_type"], + attachment: json["attachment"], + isSkip: json["is_skip"], + isFinished: json["is_finished"], + ); + + Map<String, dynamic> toMap() => { + "course_id": courseId, + "lesson_id": lessonId, + "section_title": sectionTitle, + "lesson_title": lessonTitle, + "duration": duration, + "attachment_type": attachmentType, + "video_type": videoType, + "video_url": videoUrl, + "lesson_type": lessonType, + "attachment": attachment, + "is_skip": isSkip, + "is_finished": isFinished, + }; +} diff --git a/lib/models/lesson_model.dart b/lib/models/lesson_model.dart new file mode 100644 index 0000000..95dae81 --- /dev/null +++ b/lib/models/lesson_model.dart @@ -0,0 +1,59 @@ +class LessonModel { + LessonModel({ + required this.lesssonId, + required this.title, + required this.duration, + this.videoType, + this.videoUrl, + this.attachmentType, + this.attachment, + required this.attachmentDownload, + required this.isSkip, + required this.progressVideo, + required this.isFinished, + required this.summary, + }); + + final String lesssonId; + final String title; + final String duration; + String? videoType; + String? videoUrl; + String? attachmentType; + String? attachment; + final bool attachmentDownload; + final String isSkip; + final String progressVideo; + final int isFinished; + final String summary; + + factory LessonModel.fromJson(Map<String, dynamic> json) => LessonModel( + lesssonId: json["lesson_id"], + title: json["title"], + duration: json["duration"], + videoType: json["video_type"], + videoUrl: json["video_url"], + attachmentType: json["attachment_type"], + attachment: json["attachment"], + attachmentDownload: json["attachment_download"], + isSkip: json["is_skip"], + progressVideo: json["progress_video"], + isFinished: json["is_finished"], + summary: json["summary"], + ); + + Map<String, dynamic> toJson() => { + "lesson_id": lesssonId, + "title": title, + "duration": duration, + "video_type": videoType, + "video_url": videoUrl, + "attachment_type": attachmentType, + "attachment": attachment, + "attachment_download": attachmentDownload, + "is_skip": isSkip, + "progress_video": progressVideo, + "is_finished": isFinished, + "summary": summary, + }; +} diff --git a/lib/models/models.dart b/lib/models/models.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/models/models.dart @@ -0,0 +1 @@ + diff --git a/lib/models/my_certificate.dart b/lib/models/my_certificate.dart new file mode 100644 index 0000000..1195a08 --- /dev/null +++ b/lib/models/my_certificate.dart @@ -0,0 +1,77 @@ +class MyCertificateModel { + MyCertificateModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List<List<DataMyCertificateModel>> data; + + factory MyCertificateModel.fromJson(Map<String, dynamic> json) => + MyCertificateModel( + status: json["status"], + error: json["error"], + data: List<List<DataMyCertificateModel>>.from(json["data"].map((x) => + List<DataMyCertificateModel>.from( + x.map((x) => DataMyCertificateModel.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from( + data.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class DataMyCertificateModel { + String? courseId; + String? instructorId; + String? name; + String? title; + String? instructor; + String? certificateNo; + int? progress; + String? fotoProfile; + String? idPayment; + + DataMyCertificateModel({ + this.courseId, + this.instructorId, + this.name, + this.title, + this.instructor, + this.certificateNo, + this.progress, + this.fotoProfile, + this.idPayment, + }); + + DataMyCertificateModel.fromJson(Map<String, dynamic> json) { + courseId = json['course_id']; + instructorId = json['instructor_id']; + name = json['name']; + title = json['title']; + instructor = json['instructor']; + certificateNo = json['certificate_no']; + progress = json['progress']; + fotoProfile = json['foto_profile']; + idPayment = json['id_payment']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['course_id'] = this.courseId; + data['instructor_id'] = this.instructorId; + data['name'] = this.name; + data['title'] = this.title; + data['instructor'] = this.instructor; + data['certificate_no'] = this.certificateNo; + data['progress'] = this.progress; + data['foto_profile'] = this.fotoProfile; + data['id_payment'] = this.idPayment; + return data; + } +} diff --git a/lib/models/my_course_model.dart b/lib/models/my_course_model.dart new file mode 100644 index 0000000..02c2ef2 --- /dev/null +++ b/lib/models/my_course_model.dart @@ -0,0 +1,97 @@ +class MyCourseModel { + MyCourseModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List<List<DataMyCourseModel>> data; + + factory MyCourseModel.fromJson(Map<String, dynamic> json) => MyCourseModel( + status: json["status"], + error: json["error"], + data: List<List<DataMyCourseModel>>.from(json["data"].map((x) => + List<DataMyCourseModel>.from( + x.map((x) => DataMyCourseModel.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from( + data.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class DataMyCourseModel { + DataMyCourseModel({ + this.courseId, + this.instructorId, + this.instructor, + this.title, + this.thumbnail, + required this.rating, + this.fotoProfile, + this.totalProgress, + }); + + final String? courseId; + final String? instructorId; + final String? instructor; + final String? title; + final String? thumbnail; + final List<Rating> rating; + final dynamic fotoProfile; + final int? totalProgress; + + factory DataMyCourseModel.fromJson(Map<String, dynamic> json) => + DataMyCourseModel( + courseId: json["course_id"], + instructorId: json["instructor_id"], + instructor: json["instructor"], + title: json["title"], + thumbnail: json["thumbnail"], + rating: json['rating'] == [] + ? [] + : List<Rating>.from(json["rating"].map((x) => Rating.fromJson(x))), + fotoProfile: json["foto_profile"], + totalProgress: json["total_progress"], + ); + + Map<String, dynamic> toJson() => { + "course_id": courseId, + "instructor_id": instructorId, + "instructor": instructor, + "title": title, + "thumbnail": thumbnail, + "rating": List<dynamic>.from(rating.map((x) => x.toJson())), + "foto_profile": fotoProfile, + "total_progress": totalProgress, + }; +} + +class Rating { + Rating({ + this.ratableId, + this.rating, + this.review, + }); + + final String? ratableId; + final String? rating; + final String? review; + + factory Rating.fromJson(Map<String, dynamic> json) => Rating( + ratableId: json["ratable_id"], + rating: json["rating"], + review: json["review"], + ); + + Map<String, dynamic> toJson() => { + "ratable_id": ratableId, + "rating": rating, + "review": review, + }; +} diff --git a/lib/models/notification.dart b/lib/models/notification.dart new file mode 100644 index 0000000..2527957 --- /dev/null +++ b/lib/models/notification.dart @@ -0,0 +1,385 @@ +// Ini model buat notifikasi +class Notification { + int? status; + bool? error; + Data? data; + + Notification({this.status, this.error, this.data}); + + Notification.fromJson(Map<String, dynamic> json) { + status = json['status']; + error = json['error']; + data = json['data'] != null ? new Data.fromJson(json['data']) : null; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['status'] = this.status; + data['error'] = this.error; + if (this.data != null) { + data['data'] = this.data!.toJson(); + } + return data; + } +} + +class Data { + Users? users; + Instructur? instructur; + + Data({this.users, this.instructur}); + + Data.fromJson(Map<String, dynamic> json) { + users = json['users'] != null ? new Users.fromJson(json['users']) : null; + instructur = json['instructur'] != null + ? new Instructur.fromJson(json['instructur']) + : null; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + if (this.users != null) { + data['users'] = this.users!.toJson(); + } + if (this.instructur != null) { + data['instructur'] = this.instructur!.toJson(); + } + return data; + } +} + +class Users { + List<Qna>? qna; + List<Courses>? courses; + List<Announcement>? announcement; + + Users({this.qna, this.courses, this.announcement}); + + Users.fromJson(Map<String, dynamic> json) { + if (json['qna'] != null) { + qna = <Qna>[]; + json['qna'].forEach((v) { + qna!.add(new Qna.fromJson(v)); + }); + } + if (json['announcement'] != null) { + announcement = <Announcement>[]; + json['announcement'].forEach((v) { + announcement!.add(new Announcement.fromJson(v)); + }); + } + if (json['courses'] != null) { + courses = <Courses>[]; + json['courses'].forEach((v) { + courses!.add(new Courses.fromJson(v)); + }); + } + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + if (this.qna != null) { + data['qna'] = this.qna!.map((v) => v.toJson()).toList(); + } + if (this.announcement != null) { + data['announcement'] = this.announcement!.map((v) => v.toJson()).toList(); + } + if (this.courses != null) { + data['courses'] = this.courses!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class NotificationDataAnnouncementUser { + String? idCourse; + String? subject; + String? titleCourse; + String? messages; + String? date; + String? thumbnail; + String? timestamps; + String? instructor; + int? isRead; + String? idRead; + String? ket; + + NotificationDataAnnouncementUser( + {this.idCourse, + this.subject, + this.titleCourse, + this.messages, + this.date, + this.thumbnail, + this.timestamps, + this.instructor, + this.isRead, + this.idRead, + this.ket}); + + NotificationDataAnnouncementUser.announcementFromJson( + Map<String, dynamic> json) { + idCourse = json['id_course']; + subject = json['subject']; + titleCourse = json['title_course']; + thumbnail = json['thumbnail']; + messages = json['messages']; + date = json['date']; + timestamps = json['timestamps']; + instructor = json['name_instructure']; + isRead = json['is_read']; + idRead = json['id_read']; + ket = json['ket']; + } +} + +class NotificationData { + String? idCourse; + String? subject; + String? titleCourse; + String? messages; + String? date; + String? thumbnail; + String? timestamps; + String? instructor; + String? isRead; + String? idRead; + String? ket; + + NotificationData( + {this.idCourse, + this.subject, + this.titleCourse, + this.messages, + this.date, + this.thumbnail, + this.timestamps, + this.instructor, + this.isRead, + this.idRead, + this.ket}); + + NotificationData.qnaFromJson(Map<String, dynamic> json) { + idCourse = json['id_course']; + subject = json['subject']; + titleCourse = json['title_course']; + thumbnail = json['thumbnail']; + messages = json['messages']; + date = json['date']; + timestamps = json['timestamps'].toString(); + instructor = json['name_instructure']; + isRead = json['is_read']; + idRead = json['id_read']; + ket = json['ket']; + } + + NotificationData.announcementFromJson(Map<String, dynamic> json) { + subject = json['subject']; + titleCourse = json['title_course']; + messages = json['messages']; + date = json['date']; + idCourse = json['id_course']; + timestamps = json['timestamps']; + isRead = json['is_read'].toString(); + idRead = json['id_read']; + ket = json['ket']; + instructor = json['name_instructure']; + thumbnail = json['thumbnail']; + } + + NotificationData.coursesFromJson(Map<String, dynamic> json) { + idCourse = json['id_course']; + subject = json['subject']; + titleCourse = json['title_course']; + thumbnail = json['thumbnail']; + messages = json['messages']; + date = json['date']; + timestamps = json['timestamps']; + instructor = json['instructur_name']; + isRead = json['is_read']; + idRead = json['id_read']; + ket = json['ket']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['subject'] = this.subject; + data['title_course'] = this.titleCourse; + data['messages'] = this.messages; + data['date'] = this.date; + return data; + } +} + +class Qna { + String? subject; + String? titleCourse; + String? messages; + String? date; + + Qna({this.subject, this.titleCourse, this.messages, this.date}); + + Qna.fromJson(Map<String, dynamic> json) { + subject = json['subject']; + titleCourse = json['title_course']; + messages = json['messages']; + date = json['date']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['subject'] = this.subject; + data['title_course'] = this.titleCourse; + data['messages'] = this.messages; + data['date'] = this.date; + return data; + } +} + +class Announcement { + String? subject; + String? titleCourse; + String? messages; + String? date; + + Announcement({this.subject, this.titleCourse, this.messages, this.date}); + + Announcement.fromJson(Map<String, dynamic> json) { + subject = json['subject']; + titleCourse = json['title_course']; + messages = json['messages']; + date = json['date']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['subject'] = this.subject; + data['title_course'] = this.titleCourse; + data['messages'] = this.messages; + data['date'] = this.date; + return data; + } +} + +class Courses { + String? subject; + String? title; + String? thumbnail; + String? date; + + Courses({this.subject, this.title, this.thumbnail, this.date}); + + Courses.fromJson(Map<String, dynamic> json) { + subject = json['subject']; + title = json['title']; + thumbnail = json['thumbnail']; + date = json['date']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['subject'] = this.subject; + data['title'] = this.title; + data['thumbnail'] = this.thumbnail; + data['date'] = this.date; + return data; + } +} + +class Instructur { + List<Qna>? qna; + List<Announcement>? announcement; + List<CoursesAdded>? coursesAdded; + List<Payout>? payout; + + Instructur({this.qna, this.coursesAdded, this.payout, this.announcement}); + + Instructur.fromJson(Map<String, dynamic> json) { + if (json['qna'] != null) { + qna = <Qna>[]; + json['qna'].forEach((v) { + qna!.add(new Qna.fromJson(v)); + }); + } + if (json['announcement'] != null) { + announcement = <Announcement>[]; + json['announcement'].forEach((v) { + announcement!.add(new Announcement.fromJson(v)); + }); + } + if (json['courses_added'] != null) { + coursesAdded = <CoursesAdded>[]; + json['courses_added'].forEach((v) { + coursesAdded!.add(new CoursesAdded.fromJson(v)); + }); + } + if (json['payout'] != null) { + payout = <Payout>[]; + json['payout'].forEach((v) { + payout!.add(new Payout.fromJson(v)); + }); + } + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + if (this.qna != null) { + data['qna'] = this.qna!.map((v) => v.toJson()).toList(); + } + if (this.announcement != null) { + data['announcement'] = this.announcement!.map((v) => v.toJson()).toList(); + } + if (this.coursesAdded != null) { + data['courses_added'] = + this.coursesAdded!.map((v) => v.toJson()).toList(); + } + if (this.payout != null) { + data['payout'] = this.payout!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class CoursesAdded { + String? subject; + String? title; + String? time; + + CoursesAdded({this.subject, this.title, this.time}); + + CoursesAdded.fromJson(Map<String, dynamic> json) { + subject = json['subject']; + title = json['title']; + time = json['time']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['subject'] = this.subject; + data['title'] = this.title; + data['time'] = this.time; + return data; + } +} + +class Payout { + String? subject; + String? message; + String? date; + + Payout({this.subject, this.message, this.date}); + + Payout.fromJson(Map<String, dynamic> json) { + subject = json['subject']; + message = json['message']; + date = json['date']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['subject'] = this.subject; + data['message'] = this.message; + data['date'] = this.date; + return data; + } +} diff --git a/lib/models/order_model.dart b/lib/models/order_model.dart new file mode 100644 index 0000000..3471555 --- /dev/null +++ b/lib/models/order_model.dart @@ -0,0 +1,16 @@ +class OrderModel { + String idCourse; + String title; + String price; + String discountPrice; + String imageUrl; + String instructor; + + OrderModel( + {required this.idCourse, + required this.title, + required this.price, + required this.imageUrl, + required this.discountPrice, + required this.instructor}); +} diff --git a/lib/models/payment_history_model.dart b/lib/models/payment_history_model.dart new file mode 100644 index 0000000..95d3ab3 --- /dev/null +++ b/lib/models/payment_history_model.dart @@ -0,0 +1,50 @@ +class PaymentHistoryModel { + PaymentHistoryModel({this.status, this.error, required this.data}); + final int? status; + final bool? error; + final List<List<DataPaymentHistoryModel>> data; +} + +class DataPaymentHistoryModel { + DataPaymentHistoryModel({ + this.orderId, + this.title, + this.thumbnail, + this.instructor, + this.totalPrice, + this.date, + this.paymentType, + this.statusPayment, + }); + final String? orderId; + final String? title; + final String? thumbnail; + final String? instructor; + final String? totalPrice; + final String? date; + final String? paymentType; + final String? statusPayment; + + factory DataPaymentHistoryModel.fromJson(Map<String, dynamic> json) => + DataPaymentHistoryModel( + orderId: json['order_id'], + title: json["title"], + thumbnail: json["thumbnail"] == null ? null : json["thumbnail"], + instructor: json["instructor"], + totalPrice: json["total"], + date: json["date"], + paymentType: json["payment_type"], + statusPayment: json["status"], + ); + + Map<String, dynamic> toJson() => { + "order_id": orderId, + "title": title, + "thumbnail": thumbnail == null ? null : thumbnail, + "instructor": instructor, + "total_price": totalPrice, + "date": date, + "payment_type": paymentType, + "status_payment": statusPayment, + }; +} diff --git a/lib/models/payments_model.dart b/lib/models/payments_model.dart new file mode 100644 index 0000000..2e47666 --- /dev/null +++ b/lib/models/payments_model.dart @@ -0,0 +1,23 @@ +class PaymentModel { + PaymentModel({ + this.status, + this.error, + this.messages, + }); + + final int? status; + final bool? error; + final String? messages; + + factory PaymentModel.fromJson(Map<String, dynamic> json) => PaymentModel( + status: json["status"], + error: json["error"], + messages: json["messages"], + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "messages": messages, + }; +} diff --git a/lib/models/profile_image_post_model.dart b/lib/models/profile_image_post_model.dart new file mode 100644 index 0000000..e704375 --- /dev/null +++ b/lib/models/profile_image_post_model.dart @@ -0,0 +1,40 @@ +class ProfileImagePostModel { + ProfileImagePostModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final DataPostImage data; + + factory ProfileImagePostModel.fromJson(Map<String, dynamic> json) => + ProfileImagePostModel( + status: json["status"], + error: json["error"], + data: DataPostImage.fromJson(json["data"]), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data.toJson(), + }; +} + +class DataPostImage { + DataPostImage({ + this.messages, + }); + + final String? messages; + + factory DataPostImage.fromJson(Map<String, dynamic> json) => DataPostImage( + messages: json["messages"], + ); + + Map<String, dynamic> toJson() => { + "messages": messages, + }; +} diff --git a/lib/models/qna_model.dart b/lib/models/qna_model.dart new file mode 100644 index 0000000..d38622e --- /dev/null +++ b/lib/models/qna_model.dart @@ -0,0 +1,130 @@ +import 'package:initial_folder/models/comment_qna_model.dart'; + +class QnaModel { + QnaModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List<List<QnaDataModel>> data; + + factory QnaModel.fromJson(Map<String, dynamic> json) => QnaModel( + status: json["status"], + error: json["error"], + data: List<List<QnaDataModel>>.from(json["data"].map((x) => + List<QnaDataModel>.from(x.map((x) => QnaDataModel.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from( + data.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class QnaDataModel { + QnaDataModel({ + this.idQna, + this.idLesson, + this.sender, + this.username, + this.title, + this.quest, + this.fotoProfile, + this.countComment, + this.countLike, + this.selfLiked, + this.date, + required this.comment, + }); + + final String? idQna; + final String? idLesson; + final String? sender; + final String? username; + final String? title; + final String? quest; + final String? fotoProfile; + int? countComment; + String? countLike; + bool? selfLiked; + final String? date; + List<Comment> comment; + + factory QnaDataModel.fromJson(Map<String, dynamic> json) => QnaDataModel( + idQna: json["id_qna"], + idLesson: json["id_lesson"], + sender: json["sender"], + username: json["username"], + title: json["title"], + quest: json["quest"], + fotoProfile: json["foto_profile"], + countComment: json["count_comment"], + countLike: json["count_up"], + selfLiked: json['status_up'], + date: json["date"], + comment: + List<Comment>.from(json["comment"].map((x) => Comment.fromJson(x))) + .toList(), + ); + + Map<String, dynamic> toJson() => { + "id_qna": idQna, + "id_lesson": idLesson, + "sender": sender, + "username": username, + "quest": quest, + "foto_profile": fotoProfile, + "date": date, + "comment": List<dynamic>.from(comment.map((x) => x)), + }; +} + +/// Class untuk response tambah +class QnaPostModel { + QnaPostModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final DataPostQna data; + + factory QnaPostModel.fromJson(Map<String, dynamic> json) => QnaPostModel( + status: json["status"], + error: json["error"], + data: DataPostQna.fromJson(json["data"]), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data.toJson(), + }; +} + +class DataPostQna { + DataPostQna({this.sender, this.quest, this.idCourse}); + + final String? sender; + final String? quest; + final String? idCourse; + + factory DataPostQna.fromJson(Map<String, dynamic> json) => DataPostQna( + sender: json["sender"], + quest: json["quest"], + idCourse: json["id_course"], + ); + + Map<String, dynamic> toJson() => { + "messages": sender, + "quest": quest, + "id_course": idCourse, + }; +} diff --git a/lib/models/quiz_model.dart b/lib/models/quiz_model.dart new file mode 100644 index 0000000..7faf884 --- /dev/null +++ b/lib/models/quiz_model.dart @@ -0,0 +1,82 @@ +// To parse this JSON data, do +// +// final quizModel = quizModelFromJson(jsonString); + +import 'dart:convert'; + +QuizModel quizModelFromJson(String str) => QuizModel.fromJson(json.decode(str)); + +String quizModelToJson(QuizModel data) => json.encode(data.toJson()); + +class QuizModel { + int status; + bool error; + int total; + List<Datum> data; + + QuizModel({ + required this.status, + required this.error, + required this.total, + required this.data, + }); + + factory QuizModel.fromJson(Map<String, dynamic> json) => QuizModel( + status: json["status"], + error: json["error"], + total: json["total"], + data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "total": total, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class Datum { + String id; + String quizId; + String title; + String type; + String numberOption; + List<String> options; + List<String> correctAnswers; + String order; + + Datum({ + required this.id, + required this.quizId, + required this.title, + required this.type, + required this.numberOption, + required this.options, + required this.correctAnswers, + required this.order, + }); + + factory Datum.fromJson(Map<String, dynamic> json) => Datum( + id: json["id"], + quizId: json["quiz_id"], + title: json["title"], + type: json["type"], + numberOption: json["number_option"], + options: List<String>.from(json["options"].map((x) => x)), + correctAnswers: + List<String>.from(json["correct_answers"].map((x) => x)), + order: json["order"], + ); + + Map<String, dynamic> toJson() => { + "id": id, + "quiz_id": quizId, + "title": title, + "type": type, + "number_option": numberOption, + "options": List<dynamic>.from(options.map((x) => x)), + "correct_answers": List<dynamic>.from(correctAnswers.map((x) => x)), + "order": order, + }; +} diff --git a/lib/models/quiz_perquestion_result_model.dart b/lib/models/quiz_perquestion_result_model.dart new file mode 100644 index 0000000..7be79c8 --- /dev/null +++ b/lib/models/quiz_perquestion_result_model.dart @@ -0,0 +1,81 @@ +// To parse this JSON data, do +// +// final quizPerQuestionResult = quizPerQuestionResultFromJson(jsonString); + +import 'dart:convert'; + +QuizPerQuestionResult quizPerQuestionResultFromJson(String str) => QuizPerQuestionResult.fromJson(json.decode(str)); + +String quizPerQuestionResultToJson(QuizPerQuestionResult data) => json.encode(data.toJson()); + +class QuizPerQuestionResult { + int status; + bool error; + int total; + List<Datum> data; + + QuizPerQuestionResult({ + required this.status, + required this.error, + required this.total, + required this.data, + }); + + factory QuizPerQuestionResult.fromJson(Map<String, dynamic> json) => QuizPerQuestionResult( + status: json["status"], + error: json["error"], + total: json["total"], + data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "total": total, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class Datum { + String id; + String quizId; + String title; + String type; + String numberOption; + List<String> options; + List<String> correctAnswers; + String order; + + Datum({ + required this.id, + required this.quizId, + required this.title, + required this.type, + required this.numberOption, + required this.options, + required this.correctAnswers, + required this.order, + }); + + factory Datum.fromJson(Map<String, dynamic> json) => Datum( + id: json["id"], + quizId: json["quiz_id"], + title: json["title"], + type: json["type"], + numberOption: json["number_option"], + options: List<String>.from(json["options"].map((x) => x)), + correctAnswers: List<String>.from(json["correct_answers"].map((x) => x)), + order: json["order"], + ); + + Map<String, dynamic> toJson() => { + "id": id, + "quiz_id": quizId, + "title": title, + "type": type, + "number_option": numberOption, + "options": List<dynamic>.from(options.map((x) => x)), + "correct_answers": List<dynamic>.from(correctAnswers.map((x) => x)), + "order": order, + }; +} diff --git a/lib/models/quiz_question_model.dart b/lib/models/quiz_question_model.dart new file mode 100644 index 0000000..123717d --- /dev/null +++ b/lib/models/quiz_question_model.dart @@ -0,0 +1,71 @@ +// To parse this JSON data, do +// +// final quizQuestion = quizQuestionFromJson(jsonString); + +import 'dart:convert'; + +QuizQuestion quizQuestionFromJson(String str) => + QuizQuestion.fromJson(json.decode(str)); + +String quizQuestionToJson(QuizQuestion data) => json.encode(data.toJson()); + +class QuizQuestion { + final List<Datum> data; + + QuizQuestion({ + required this.data, + }); + + factory QuizQuestion.fromJson(Map<String, dynamic> json) => QuizQuestion( + data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class Datum { + final String id; + final String quizId; + final String title; + final String type; + final String numberOption; + final List<String> options; + final List<String> correctAnswers; + final String order; + + Datum({ + required this.id, + required this.quizId, + required this.title, + required this.type, + required this.numberOption, + required this.options, + required this.correctAnswers, + required this.order, + }); + + factory Datum.fromJson(Map<String, dynamic> json) => Datum( + id: json["id"], + quizId: json["quiz_id"], + title: json["title"], + type: json["type"], + numberOption: json["number_option"], + options: List<String>.from(json["options"].map((x) => x)), + correctAnswers: + List<String>.from(json["correct_answers"].map((x) => x)), + order: json["order"], + ); + + Map<String, dynamic> toJson() => { + "id": id, + "quiz_id": quizId, + "title": title, + "type": type, + "number_option": numberOption, + "options": List<dynamic>.from(options.map((x) => x)), + "correct_answers": List<dynamic>.from(correctAnswers.map((x) => x)), + "order": order, + }; +} diff --git a/lib/models/quiz_question_result_model.dart b/lib/models/quiz_question_result_model.dart new file mode 100644 index 0000000..d1b4954 --- /dev/null +++ b/lib/models/quiz_question_result_model.dart @@ -0,0 +1,69 @@ +// To parse this JSON data, do +// +// final quizQuestionResult = quizQuestionResultFromJson(jsonString); + +import 'dart:convert'; + +QuizQuestionResult quizQuestionResultFromJson(String str) => + QuizQuestionResult.fromJson(json.decode(str)); + +String quizQuestionResultToJson(QuizQuestionResult data) => + json.encode(data.toJson()); + +class QuizQuestionResult { + final int status; + final bool error; + final List<Datum> data; + + QuizQuestionResult({ + required this.status, + required this.error, + required this.data, + }); + + factory QuizQuestionResult.fromJson(Map<String, dynamic> json) => + QuizQuestionResult( + status: json["status"], + error: json["error"], + data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class Datum { + final int questionId; + final List<String> answers; + final String question; + final bool isCorrect; + final List<String> correctAnswers; + + Datum({ + required this.questionId, + required this.answers, + required this.question, + required this.isCorrect, + required this.correctAnswers, + }); + + factory Datum.fromJson(Map<String, dynamic> json) => Datum( + questionId: json["question_id"], + answers: List<String>.from(json["answers"].map((x) => x)), + question: json["question"], + isCorrect: json["is_correct"], + correctAnswers: + List<String>.from(json["correct_answers"].map((x) => x)), + ); + + Map<String, dynamic> toJson() => { + "question_id": questionId, + "answers": List<dynamic>.from(answers.map((x) => x)), + "question": question, + "is_correct": isCorrect, + "correct_answers": List<dynamic>.from(correctAnswers.map((x) => x)), + }; +} diff --git a/lib/models/rating_course_model.dart b/lib/models/rating_course_model.dart new file mode 100644 index 0000000..28bec19 --- /dev/null +++ b/lib/models/rating_course_model.dart @@ -0,0 +1,19 @@ +class Rating { + Rating({ + this.totalReview, + this.avgRating, + }); + + String? totalReview; + int? avgRating; + + factory Rating.fromJson(Map<String, dynamic> json) => Rating( + totalReview: json["total_review"], + avgRating: json["avg_rating"], + ); + + Map<String, dynamic> toJson() => { + "total_review": totalReview, + "avg_rating": avgRating, + }; +} diff --git a/lib/models/reply_announcement_model.dart b/lib/models/reply_announcement_model.dart new file mode 100644 index 0000000..424b70c --- /dev/null +++ b/lib/models/reply_announcement_model.dart @@ -0,0 +1,55 @@ +class ReplyModel { + ReplyModel({ + this.idRep, + this.sender, + this.name, + this.body, + this.fotoProfile, + this.updateAt, + this.createAt, + }); + + String? idRep; + String? sender; + String? name; + String? body; + String? fotoProfile; + String? updateAt; + String? createAt; + + factory ReplyModel.fromJson(Map<String, dynamic> json) => ReplyModel( + idRep: json["id_replies"], + sender: json["sender"], + name: json["name"], + body: json["body"], + fotoProfile: json["foto_profile"], + createAt: json["created_at"], + updateAt: json["update_at"], + ); + + Map<String, dynamic> toJson() => { + "id_replies": idRep, + "sender": sender, + "name": name, + "body": body, + "foto_profile": fotoProfile, + "update_at": updateAt, + "created_at": createAt, + }; +} + +class LikeModels { + LikeModels({ + this.userId, + }); + + bool? userId; + + factory LikeModels.fromJson(Map<String, dynamic> json) => LikeModels( + userId: json["user_id"].length > 0, + ); + + Map<String, dynamic> toJson() => { + "id_replies": userId, + }; +} diff --git a/lib/models/reset_model.dart b/lib/models/reset_model.dart new file mode 100644 index 0000000..27bffce --- /dev/null +++ b/lib/models/reset_model.dart @@ -0,0 +1,15 @@ +class ResetModel{ + String? token; + String? email; + ResetModel({this.email, this.token}); + + factory ResetModel.fromJson(Map<String, dynamic> json) => ResetModel( + token: json["token"], + email: json["email"], + ); + + Map<String, dynamic> toJson() => { + "token": token, + "email": email, + }; +} \ No newline at end of file diff --git a/lib/models/section_lesson_model.dart b/lib/models/section_lesson_model.dart new file mode 100644 index 0000000..9867f86 --- /dev/null +++ b/lib/models/section_lesson_model.dart @@ -0,0 +1,78 @@ +class SectionLessonModel { + SectionLessonModel({ + this.status, + this.error, + this.data, + }); + + final int? status; + final bool? error; + final List<List<SectionLessonList>>? data; + + factory SectionLessonModel.fromJson(Map<String, dynamic> json) => + SectionLessonModel( + status: json["status"], + error: json["error"], + data: List<List<SectionLessonList>>.from(json["data"].map((x) => + List<SectionLessonList>.from( + x.map((x) => SectionLessonList.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from( + data!.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class SectionLessonList { + SectionLessonList({ + this.title, + this.duration, + this.dataLesson, + }); + + final String? title; + final String? duration; + final List<List<DataLesson>>? dataLesson; + + factory SectionLessonList.fromJson(Map<String, dynamic> json) => + SectionLessonList( + title: json["title"], + duration: json["duration"] == null ? null : json["duration"], + dataLesson: List<List<DataLesson>>.from(json["data_lesson"].map((x) => + List<DataLesson>.from(x.map((x) => DataLesson.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "title": title, + "duration": duration == null ? null : duration, + "data_lesson": List<dynamic>.from(dataLesson! + .map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class DataLesson { + DataLesson( + {this.titleLesson, this.duration, this.lessonType, this.attachment}); + + final String? titleLesson; + final String? duration; + final String? lessonType; + final String? attachment; + + factory DataLesson.fromJson(Map<String, dynamic> json) => DataLesson( + titleLesson: json["title_lesson"], + duration: json["duration"], + lessonType: json["lesson_type"], + attachment: json["attachment"], + ); + + Map<String, dynamic> toJson() => { + "title_lesson": titleLesson, + "duration": duration, + "lesson_type": lessonType, + "attachment": attachment, + }; +} diff --git a/lib/models/section_model.dart b/lib/models/section_model.dart new file mode 100644 index 0000000..5addf3d --- /dev/null +++ b/lib/models/section_model.dart @@ -0,0 +1,187 @@ +class SectionModel { + SectionModel({ + this.status, + this.error, + this.progress, + required this.data, + }); + + int? status; + bool? error; + int? progress; + List<Map<String, Datum>> data; + + factory SectionModel.fromJson(Map<String, dynamic> json) => SectionModel( + status: json["status"], + error: json["error"], + progress: json["progress"], + data: List<Map<String, Datum>>.from(json["data"].map((x) => Map.from(x) + .map((k, v) => MapEntry<String, Datum>(k, Datum.fromJson(v))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "progress": progress, + "data": List<dynamic>.from(data.map((x) => Map.from(x) + .map((k, v) => MapEntry<String, dynamic>(k, v.toJson())))), + }; +} + +class Datum { + Datum({ + this.sectionTitle, + this.dataLesson, + }); + + String? sectionTitle; + List<DataLesson>? dataLesson; + + factory Datum.fromJson(Map<String, dynamic> json) => Datum( + sectionTitle: json["section_title"], + dataLesson: List<DataLesson>.from( + json["data_lesson"].map((x) => DataLesson.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "section_title": sectionTitle, + "data_lesson": List<dynamic>.from(dataLesson!.map((x) => x.toJson())), + }; +} + +class DataLesson { + DataLesson( + {this.lessonId, + this.title, + this.duration, + this.progress, + this.attachmentType, + this.videoType, + this.videoUrl, + this.lessonType, + this.attachment, + this.isSkip, + this.isFinished, + this.summary}); + + String? progress; + String? lessonId; + String? title; + String? duration; + String? attachmentType; + String? videoType; + String? videoUrl; + String? lessonType; + dynamic attachment; + String? isSkip; + String? summary; + int? isFinished; + + factory DataLesson.fromJson(Map<String, dynamic> json) => DataLesson( + lessonId: json["lesson_id"], + title: json["title"], + duration: json["duration"], + progress: json["progress_video"], + attachmentType: json["attachment_type"], + videoType: json["video_type"], + videoUrl: json["video_url"], + lessonType: json["lesson_type"], + attachment: json["attachment"], + isSkip: json["is_skip"], + isFinished: json["is_finished"], + summary: json["summary"], + ); + + get courseId => null; + + Map<String, dynamic> toJson() => { + "lesson_id": lessonId, + "title": title, + "duration": duration, + "progress_video": progress, + "attachment_type": attachmentType, + "video_type": videoType, + "video_url": videoUrl, + "lesson_type": lessonType, + "attachment": attachment, + "is_skip": isSkip, + "is_finished": isFinished, + "summary": summary, + }; +} + +class NewMap { + NewMap({ + required this.title, + }); + + final List<Title> title; + + factory NewMap.fromMap(Map<String, dynamic> json) => NewMap( + title: List<Title>.from(json["Title"].map((x) => Title.fromMap(x))), + ); + + Map<String, dynamic> toMap() => { + "Title": List<dynamic>.from(title.map((x) => x.toMap())), + }; +} + +class Title { + Title({ + this.courseId, + this.lessonId, + this.sectionTitle, + this.lessonTitle, + this.duration, + this.attachmentType, + this.videoType, + this.videoUrl, + this.lessonType, + this.attachment, + this.isSkip, + this.isFinished, + }); + + final String? courseId; + final String? lessonId; + final String? sectionTitle; + final String? lessonTitle; + final String? duration; + final String? attachmentType; + final String? videoType; + final String? videoUrl; + final String? lessonType; + final dynamic attachment; + final String? isSkip; + final int? isFinished; + + factory Title.fromMap(Map<String, dynamic> json) => Title( + courseId: json["course_id"], + lessonId: json["lesson_id"], + sectionTitle: json["section_title"], + lessonTitle: json["lesson_title"], + duration: json["duration"], + attachmentType: json["attachment_type"], + videoType: json["video_type"], + videoUrl: json["video_url"] == '' ? 'a' : json["video_url"], + lessonType: json["lesson_type"], + attachment: json["attachment"], + isSkip: json["is_skip"], + isFinished: json["is_finished"], + ); + + Map<String, dynamic> toMap() => { + "course_id": courseId, + "lesson_id": lessonId, + "section_title": sectionTitle, + "lesson_title": lessonTitle, + "duration": duration, + "attachment_type": attachmentType, + "video_type": videoType, + "video_url": videoUrl, + "lesson_type": lessonType, + "attachment": attachment, + "is_skip": isSkip, + "is_finished": isFinished, + }; +} diff --git a/lib/models/social_link_model.dart b/lib/models/social_link_model.dart new file mode 100644 index 0000000..5223c2c --- /dev/null +++ b/lib/models/social_link_model.dart @@ -0,0 +1,15 @@ +class SocialLink { + SocialLink({ + this.facebook, + }); + + String? facebook; + + factory SocialLink.fromJson(Map<String, dynamic> json) => SocialLink( + facebook: json["facebook"], + ); + + Map<String, dynamic> toJson() => { + "facebook": facebook, + }; +} diff --git a/lib/models/subcategories_model.dart b/lib/models/subcategories_model.dart new file mode 100644 index 0000000..a6ad829 --- /dev/null +++ b/lib/models/subcategories_model.dart @@ -0,0 +1,23 @@ +class SubCategoryModel { + final String subId; + final String nameSubCategory; + + SubCategoryModel({ + required this.subId, + required this.nameSubCategory, + }); + + factory SubCategoryModel.fromJson(Map<String, dynamic> json) { + return SubCategoryModel( + subId: json['id'], + nameSubCategory: json['name_subcategory'], + ); + } + + Map<String, dynamic> toJson() { + return { + 'id': subId, + 'name_subcategory': nameSubCategory, + }; + } +} diff --git a/lib/models/update_data_diri_model.dart b/lib/models/update_data_diri_model.dart new file mode 100644 index 0000000..458c20a --- /dev/null +++ b/lib/models/update_data_diri_model.dart @@ -0,0 +1,43 @@ +class UpdateDataDiriModel { + UpdateDataDiriModel({ + this.status, + this.error, + this.messages, + }); + + int? status; + bool? error; + dynamic messages; + + factory UpdateDataDiriModel.fromJson(Map<String, dynamic> json) => UpdateDataDiriModel( + status: json["status"], + error: json["error"], + messages: json["status"] != 200 + ? MessagesOfUpdateDataDiriModel.fromJson(json["data"]["message"]) + : json["data"]["messages"], + ); +} + +class MessagesOfUpdateDataDiriModel { + MessagesOfUpdateDataDiriModel({ + this.fullname, + this.phone, + this.datebirth, + this.email, + this.gender, + }); + + String? fullname; + String? phone; + String? datebirth; + String? email; + String? gender; + + factory MessagesOfUpdateDataDiriModel.fromJson(Map<String, dynamic> json) => MessagesOfUpdateDataDiriModel( + fullname: json["full_name"], + phone: json["phone"], + datebirth: json["datebirth"], + email: json["email"], + gender: json["jenis_kel"], + ); +} \ No newline at end of file diff --git a/lib/models/update_incomplete_profile_model.dart b/lib/models/update_incomplete_profile_model.dart new file mode 100644 index 0000000..680c216 --- /dev/null +++ b/lib/models/update_incomplete_profile_model.dart @@ -0,0 +1,57 @@ +class UpdateIncompleteProfileModel { + UpdateIncompleteProfileModel({ + this.status, + this.error, + this.messages, + }); + + int? status; + bool? error; + dynamic messages; + + factory UpdateIncompleteProfileModel.fromJson(Map<String, dynamic> json) { + UpdateIncompleteProfileModel updateIncompleteProfileModel; + try { + updateIncompleteProfileModel = UpdateIncompleteProfileModel( + status: json["status"], + error: json["error"], + messages: json["status"] == 200 + ? json["data"]["messages"] + : json["status"] == 404 + ? json["message"] + : MessagesOfIncompleteProfileModel.fromJson(json["data"]["message"]) + ); + } catch(e) { + updateIncompleteProfileModel = UpdateIncompleteProfileModel( + status: json["status"], + error: json["error"], + messages: json["data"]["message"] + ); + } + return updateIncompleteProfileModel; + } +} + +class MessagesOfIncompleteProfileModel { + MessagesOfIncompleteProfileModel({ + this.fullname, + this.phone, + this.datebirth, + this.email, + this.gender, + }); + + String? fullname; + String? phone; + String? datebirth; + String? email; + String? gender; + + factory MessagesOfIncompleteProfileModel.fromJson(Map<String, dynamic> json) => MessagesOfIncompleteProfileModel( + fullname: json["full_name"], + phone: json["phone"], + datebirth: json["datebirth"], + email: json["email"], + gender: json["jenis_kel"], + ); +} \ No newline at end of file diff --git a/lib/models/update_password_model.dart b/lib/models/update_password_model.dart new file mode 100644 index 0000000..10e5d66 --- /dev/null +++ b/lib/models/update_password_model.dart @@ -0,0 +1,47 @@ +class UpdatePasswordModel { + UpdatePasswordModel({ + this.status, + this.error = false, + required this.data, + }); + + final int? status; + final bool error; + final List<Data> data; + + factory UpdatePasswordModel.fromJson(Map<String, dynamic> json) => + UpdatePasswordModel( + status: json["status"], + error: json["error"], + data: List<Data>.from(json["data"].map((x) => Data.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class Data { + Data({ + this.message, + + //required this.social_link, + }); + + final String? message; + //final List<SocialLink?> social_link; + + factory Data.fromJson(Map<String, dynamic> json) => Data( + message: json["message"], + //social_link: List<SocialLink>.from( + //json["social_link"].map((x) => SocialLink.fromJson(x))).toList(), + ); + + Map<String, dynamic> toJson() => { + "message": message, + //"social_link": + // List<dynamic>.from(social_link.map((x) => x!.toJson())).toList(), + }; +} diff --git a/lib/models/user_info_incomplete_model.dart b/lib/models/user_info_incomplete_model.dart new file mode 100644 index 0000000..ce93a15 --- /dev/null +++ b/lib/models/user_info_incomplete_model.dart @@ -0,0 +1,53 @@ +class UserInfoIncompleteModel { + UserInfoIncompleteModel({ + this.status, + this.error, + this.data, + }); + + final int? status; + final bool? error; + final Data? data; + + factory UserInfoIncompleteModel.fromJson(Map<String, dynamic> json) => UserInfoIncompleteModel( + status: json["status"], + error: json["error"], + data: Data.fromJson(json["data"][0]), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data == null + ? null + : [data!.toJson()], + }; +} + +class Data { + Data({ + this.idUser, + this.fullname, + this.email, + this.phone, + }); + + final String? idUser; + final String? fullname; + final String? email; + final String? phone; + + factory Data.fromJson(Map<String, dynamic> json) => Data( + idUser: json["id_user"], + fullname: json["full_name"], + email: json["email"], + phone: json["phone"], + ); + + Map<String, dynamic> toJson() => { + "id_user": idUser, + "full_name": fullname, + "email": email, + "phone": phone, + }; +} diff --git a/lib/models/user_info_model.dart b/lib/models/user_info_model.dart new file mode 100644 index 0000000..e5a91b6 --- /dev/null +++ b/lib/models/user_info_model.dart @@ -0,0 +1,55 @@ +class UserInfoModel { + UserInfoModel({ + this.status, + this.error = false, + required this.data, + }); + + final int? status; + final bool error; + final List<Data> data; + + factory UserInfoModel.fromJson(Map<String, dynamic> json) => UserInfoModel( + status: json["status"], + error: json["error"], + data: List<Data>.from(json["data"].map((x) => Data.fromJson(x))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from(data.map((x) => x.toJson())), + }; +} + +class Data { + Data({ + this.idUser, + this.fullname, + this.email, + this.fotoProfile, + this.isInstructor, + }); + + final String? idUser; + final String? fullname; + final String? email; + final String? fotoProfile; + final int? isInstructor; + + factory Data.fromJson(Map<String, dynamic> json) => Data( + idUser: json["id_user"], + fullname: json["fullname"], + email: json["email"], + fotoProfile: json["foto_profile"], + isInstructor: json["is_instructor"], + ); + + Map<String, dynamic> toJson() => { + "id_user": idUser, + "fullname": fullname, + "email": email, + "foto_profile": fotoProfile, + "is_instructor": isInstructor, + }; +} diff --git a/lib/models/user_model.dart b/lib/models/user_model.dart new file mode 100644 index 0000000..e890c6f --- /dev/null +++ b/lib/models/user_model.dart @@ -0,0 +1,23 @@ +class UserModel { + UserModel({ + this.messages, + this.token, + this.expireAt, + }); + + final String? messages; + String? token; + final int? expireAt; + + factory UserModel.fromJson(Map<String, dynamic> json) => UserModel( + messages: json["messages"], + token: json["token"], + expireAt: json["expire_at"], + ); + + Map<String, dynamic> toJson() => { + "messages": messages, + "token": token, + "expire_at": expireAt, + }; +} diff --git a/lib/models/voucher_model.dart b/lib/models/voucher_model.dart new file mode 100644 index 0000000..37476d7 --- /dev/null +++ b/lib/models/voucher_model.dart @@ -0,0 +1,90 @@ +class VoucherModel { + int? status; + bool? error; + List<DataVoucher>? data; + + VoucherModel({this.status, this.error, this.data}); + + VoucherModel.fromJson(Map<String, dynamic> json) { + status = + json['status'] is String ? int.parse(json['status']) : json['status']; + error = json['error']; + if (json['data'] != null) { + data = <DataVoucher>[]; + json['data'].forEach((v) { + data!.add(new DataVoucher.fromJson(v)); + }); + } + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['status'] = this.status; + data['error'] = this.error; + if (this.data != null) { + data['data'] = this.data!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class DataVoucher { + String? idCourse; + String? typeCoupon; + String? value; + String? discountFlag; + String? courseName; + String? instructor; + String? fotoProfile; + String? thubmnail; + String? originalPrice; + dynamic discountPrice; + dynamic finalPrice; + + DataVoucher( + {this.idCourse, + this.typeCoupon, + this.value, + this.discountFlag, + this.courseName, + this.instructor, + this.fotoProfile, + this.thubmnail, + this.originalPrice, + this.discountPrice, + this.finalPrice}); + + DataVoucher.fromJson(Map<String, dynamic> json) { + idCourse = json['id_course']; + typeCoupon = json['type_coupon']; + value = json['value']; + discountFlag = json['discount_flag']; + courseName = json['course_name']; + instructor = json['instructor']; + fotoProfile = json['foto_profile']; + thubmnail = json['thubmnail']; + originalPrice = json['original_price']; + discountPrice = json['discount_price'] is String + ? int.parse(json['discount_price']) + : json['discount_price']; + finalPrice = json['final_price'] is String + ? int.parse(json['final_price']) + : json['final_price']; + } + + Map<String, dynamic> toJson() { + final Map<String, dynamic> data = new Map<String, dynamic>(); + data['id_course'] = this.idCourse; + data['type_coupon'] = this.typeCoupon; + data['value'] = this.value; + data['discount_flag'] = this.discountFlag; + data['course_name'] = this.courseName; + data['instructor'] = this.instructor; + data['foto_profile'] = this.fotoProfile; + data['thubmnail'] = this.thubmnail; + data['original_price'] = this.originalPrice; + data['discount_price'] = this.discountPrice; + data['final_price'] = this.finalPrice; + return data; + } +} diff --git a/lib/models/wishlist_model.dart b/lib/models/wishlist_model.dart new file mode 100644 index 0000000..19d51d5 --- /dev/null +++ b/lib/models/wishlist_model.dart @@ -0,0 +1,152 @@ +/// Class untuk mengambil data wishlist + +class WishlistModel { + WishlistModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final List<List<DataWihslistModel>> data; + + factory WishlistModel.fromJson(Map<String, dynamic> json) => WishlistModel( + status: json["status"], + error: json["error"], + data: List<List<DataWihslistModel>>.from(json["data"].map((x) => + List<DataWihslistModel>.from( + x.map((x) => DataWihslistModel.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from( + data.map((x) => List<dynamic>.from(x.map((x) => x.toJson())))), + }; +} + +class DataWihslistModel { + DataWihslistModel({ + this.wishlistId, + this.courseId, + this.title, + this.price, + this.instructor, + this.thumbnail, + this.discountPrice, + this.discountFlag, + this.totalDiscount, + this.student, + required this.review, + this.fotoProfile, + }); + + final String? wishlistId; + final String? courseId; + final String? title; + final String? price; + final String? instructor; + final String? thumbnail; + final String? discountPrice; + final String? discountFlag; + final int? totalDiscount; + final String? student; + final List<Review> review; + final dynamic fotoProfile; + + factory DataWihslistModel.fromJson(Map<String, dynamic> json) => + DataWihslistModel( + wishlistId: json["wishlist_id"], + courseId: json["course_id"], + title: json["title"], + price: json["price"], + instructor: json["instructor"], + thumbnail: json["thumbnail"] == null ? null : json["thumbnail"], + discountPrice: json["discount_price"], + discountFlag: json["discount_flag"], + totalDiscount: json["total_discount"], + student: json["student"], + review: + List<Review>.from(json["review"].map((x) => Review.fromJson(x))), + fotoProfile: json["foto_profile"], + ); + + Map<String, dynamic> toJson() => { + "wishlist_id": wishlistId, + "course_id": courseId, + "title": title, + "price": price, + "instructor": instructor, + "thumbnail": thumbnail == null ? null : thumbnail, + "discount_price": discountPrice, + "discount_flag": discountFlag, + "total_discount": totalDiscount, + "student": student, + "review": List<dynamic>.from(review.map((x) => x.toJson())), + "foto_profile": fotoProfile, + }; +} + +class Review { + Review({ + this.totalReview, + this.avgRating, + }); + + final String? totalReview; + final int? avgRating; + + factory Review.fromJson(Map<String, dynamic> json) => Review( + totalReview: json["total_review"], + avgRating: json["avg_rating"] == null ? null : json["avg_rating"], + ); + + Map<String, dynamic> toJson() => { + "total_review": totalReview, + "avg_rating": avgRating == null ? null : avgRating, + }; +} + +class WishlistPostModel { + WishlistPostModel({ + this.status, + this.error, + required this.data, + }); + + final int? status; + final bool? error; + final DataPostWishlist data; + + factory WishlistPostModel.fromJson(Map<String, dynamic> json) => + WishlistPostModel( + status: json["status"], + error: json["error"], + data: DataPostWishlist.fromJson(json["data"]), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": data.toJson(), + }; +} + +class DataPostWishlist { + DataPostWishlist({ + this.messages, + }); + + final String? messages; + + factory DataPostWishlist.fromJson(Map<String, dynamic> json) => + DataPostWishlist( + messages: json["messages"], + ); + + Map<String, dynamic> toJson() => { + "messages": messages, + }; +} diff --git a/lib/models/zero_price_model.dart b/lib/models/zero_price_model.dart new file mode 100644 index 0000000..313b581 --- /dev/null +++ b/lib/models/zero_price_model.dart @@ -0,0 +1,54 @@ +// To parse this JSON data, do +// +// final zeroPrice = zeroPriceFromJson(jsonString); + +import 'dart:convert'; + +ZeroPrice zeroPriceFromJson(String str) => ZeroPrice.fromJson(json.decode(str)); + +String zeroPriceToJson(ZeroPrice data) => json.encode(data.toJson()); + +class ZeroPrice { + ZeroPrice({ + this.status, + this.error, + this.data, + }); + + final int? status; + final bool? error; + List<Data>? data; + + factory ZeroPrice.fromJson(Map<String, dynamic> json) => ZeroPrice( + status: json["status"], + error: json["error"], + data: List<Data>.from(json["data"] + .map((x) => List<Data>.from(x.map((x) => Data.fromJson(x))))), + ); + + Map<String, dynamic> toJson() => { + "status": status, + "error": error, + "data": List<dynamic>.from(data!.map((x) => x.toJson())), + }; +} + +class Data { + Data({ + this.orderId, + this.grossAmount, + }); + + final String? orderId; + final String? grossAmount; + + factory Data.fromJson(Map<String, dynamic> json) => Data( + orderId: json["order_id"], + grossAmount: json["gross_amount"], + ); + + Map<String, dynamic> toJson() => { + "order_id": orderId, + "gross_amount": grossAmount, + }; +} diff --git a/lib/providers/.gitkeep b/lib/providers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/providers/announcement_provider.dart b/lib/providers/announcement_provider.dart new file mode 100644 index 0000000..8d07ccb --- /dev/null +++ b/lib/providers/announcement_provider.dart @@ -0,0 +1,98 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/services/announcement_service.dart'; +import 'package:rxdart/rxdart.dart'; + +enum ResultState { loading, noData, hasData, error } + +enum ResultStateLike { loading, error } + +class AnnouncementProvider with ChangeNotifier { + final _service = AnnouncementService(); + + StreamController<AnnouncementModel> streamController = BehaviorSubject(); + String _message = ''; + String get message => _message; + + ResultState? _state; + ResultState? get state => _state; + + AnnouncementModel? _announcementModel; + AnnouncementModel? get result => _announcementModel; + + set annoucementModel(AnnouncementModel? announcementModel) { + _announcementModel = announcementModel; + notifyListeners(); + } + + Stream<AnnouncementModel> get announcementStream { + return streamController.stream; + } + + Future<void> getAnnouncement(String id) async { + try { + AnnouncementModel announcementModel = await _service.getAnnouncement(id); + if (announcementModel.error == true && announcementModel.status == 404) { + // Jika respons adalah 404 (tidak ditemukan), tangani objek dummy di sini + // Misalnya, menampilkan pesan kepada pengguna bahwa tidak ada data yang ditemukan + // Atau menampilkan UI yang sesuai + print('masuk rerror dsiinii'); + streamController.add(announcementModel); + } else { + // Jika respons adalah 200 (berhasil), tambahkan data ke dalam stream + streamController.add(announcementModel); + } + } catch (e) { + throw Error; + } + } + + Future<bool> likeAnnouncement(String tokenAnnouncement) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = + await AnnouncementService().likeAnnouncement(tokenAnnouncement); + if (response) { + _state = ResultState.hasData; + notifyListeners(); + return true; + } else { + _state = ResultState.noData; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.error; + notifyListeners(); + return false; + } + } + + Future<bool> replyAnnouncement(String textBody, String idAnnouncmenet, + String tokenAnnouncement, String idAnnouncement) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await AnnouncementService() + .replyAnnouncement(tokenAnnouncement, textBody, idAnnouncement); + if (response) { + _state = ResultState.hasData; + notifyListeners(); + return true; + } else { + _state = ResultState.noData; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.error; + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart new file mode 100644 index 0000000..ad342c9 --- /dev/null +++ b/lib/providers/auth_provider.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/user_model.dart'; +import 'package:initial_folder/services/auth_service.dart'; + +class AuthProvider with ChangeNotifier { + UserModel? _user; + UserModel? get user => _user; + + set user(UserModel? user) { + _user = user; + notifyListeners(); + } + + Future<bool> register({ + required String name, + required String email, + required String password, + required String phoneNumber, + }) async { + try { + UserModel? user = await AuthService().register( + name: name, + email: email, + password: password, + phoneNumber: phoneNumber, + ); + + _user = user; + return true; + } catch (e) { + print("Exception: $e"); + throw e; + } + } + + Future<bool> login({ + required String email, + required String password, + }) async { + try { + UserModel user = await AuthService().login( + email: email, + password: password, + ); + + _user = user; + return true; + } catch (e) { + print("EXception: $e"); + return false; + } + } +} diff --git a/lib/providers/banners_provider.dart b/lib/providers/banners_provider.dart new file mode 100644 index 0000000..06bc78e --- /dev/null +++ b/lib/providers/banners_provider.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/banners_model.dart'; +import 'package:initial_folder/services/banners_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class BannersProvider with ChangeNotifier { + final BannersService bannersService; + BannersProvider({required this.bannersService}) { + getAllBanners(); + } + List<BannersModel> _banners = []; + + ResultState? _state; + + String _message = ''; + String _status = ''; + + List<BannersModel> get result => _banners; + + ResultState? get state => _state; + + String get message => _message; + String get status => _status; + + set banners(List<BannersModel> banners) { + _banners = banners; + notifyListeners(); + } + + Future<dynamic> getAllBanners() async { + try { + _state = ResultState.Loading; + notifyListeners(); + var result = await bannersService.getAllBanners(); + if (result['status'] == 404) { + _state = ResultState.NoData; + print("Harusnya ke sini"); + notifyListeners(); + return _message = 'Banner kosong'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _banners = result['data']; + } + } catch (e) { + _state = ResultState.Error; + print('Gagal banner provider $e'); + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/cart_provider.dart b/lib/providers/cart_provider.dart new file mode 100644 index 0000000..f50ae02 --- /dev/null +++ b/lib/providers/cart_provider.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/cart_model.dart'; +import 'package:initial_folder/services/cart_service.dart'; + +enum ResultState { loading, succes, failed } + +class CartProvider with ChangeNotifier { + final CartService cartService; + CartProvider({required this.cartService}); + CartModel? _cartModel; + CartModel? get cartModel => _cartModel; + ResultState? _state; + ResultState? get state => _state; + set cartModel(CartModel? cartModel) { + _cartModel = cartModel; + notifyListeners(); + } + + Future<bool> addCart(_idCourse) async { + try { + _state = ResultState.loading; + notifyListeners(); + bool cart = await cartService.addCart(_idCourse); + if (cart) { + _state = ResultState.succes; + notifyListeners(); + } else if (!cart) { + _state = ResultState.failed; + notifyListeners(); + } + return true; + } on SocketException { + return false; + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + print("Exceptions: $e"); + return false; + } + } + + Future<bool> deleteCart(_idCart) async { + try { + bool cart = await cartService.deleteCart(_idCart); + if (cart) { + _state = ResultState.succes; + notifyListeners(); + } else if (!cart) { + _state = ResultState.failed; + notifyListeners(); + } + return true; + } on SocketException { + return false; + } catch (e) { + print("Exceptions: $e"); + return false; + } + } +} diff --git a/lib/providers/carts_provider.dart b/lib/providers/carts_provider.dart new file mode 100644 index 0000000..42ba631 --- /dev/null +++ b/lib/providers/carts_provider.dart @@ -0,0 +1,78 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/carts_model.dart'; +import 'package:initial_folder/services/cart_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class CartsProvider with ChangeNotifier { + final CartService cartService; + CartsProvider({required this.cartService}) { + getCarts(); + } + + CartsModel? _cartsModel; + CartsModel? get result => _cartsModel; + + ResultState? _state; + ResultState? get state => _state; + + String _message = ''; + String _finalPrice = ''; + + String get message => _message; + String get finalPrice => _finalPrice; + + int _length = 0; + int get lenght => _length; + + set lengthCarts(int v) { + _length = v; + notifyListeners(); + } + + List _data = []; + List get data => _data; + + // StreamController<List<DataCartsModel>> dataStream = StreamController.broadcast(); + + set carts(CartsModel carts) { + _cartsModel = carts; + notifyListeners(); + } + + void clear() { + _length = 0; + notifyListeners(); + } + + Future<dynamic> getCarts() async { + try { + _state = ResultState.Loading; + _data = []; + notifyListeners(); + CartsModel carts = await cartService.getCarts(); + _length = carts.data.length; + notifyListeners(); + if (carts.data.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _data = carts.data.map((e) => e.courseId).toList(); + _state = ResultState.HasData; + print(_data); + print("ini dari getcatsrs"); + notifyListeners(); + return _cartsModel = carts; + } + } catch (e) { + _state = ResultState.Error; + log('$e'); + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/categories_provider.dart b/lib/providers/categories_provider.dart new file mode 100644 index 0000000..e8b1dad --- /dev/null +++ b/lib/providers/categories_provider.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/catagories_model.dart'; +import 'package:initial_folder/models/subcategories_model.dart'; +import 'package:initial_folder/services/categories_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class CategoriesProvider with ChangeNotifier { + final CategoriesService categoriesService; + CategoriesProvider({required this.categoriesService}) { + getAllCategories(); + } + List<CategoriesModel> _categories = []; + Map<String, List<SubCategoryModel>> _subCategoriesMap = {}; + + ResultState? _state; + + String _message = ''; + + List<String?> get ids { + return _categories.map((category) => category.id).toList(); + } + + List<String?> get nameCategories { + return _categories.map((category) => category.nameCategory).toList(); + } + + List<CategoriesModel> get result => _categories; + + ResultState? get state => _state; + + String get message => _message; + + String _selectedSubcategoryId = ''; + + set categories(List<CategoriesModel> categories) { + _categories = categories; + notifyListeners(); + } + + Map<String, List<SubCategoryModel>> get subCategoriesMap => _subCategoriesMap; + + String get selectedSubcategoryId => _selectedSubcategoryId; + + set selectedSubcategoryId(String value) { + _selectedSubcategoryId = value; + notifyListeners(); + } + + Future<dynamic> getAllCategories() async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CategoriesModel> categories = + await categoriesService.getAllCategories(); + if (categories.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + for (var category in categories) { + List<SubCategoryModel> subCategories = + await categoriesService.getSubCategories(category.id!); + category.subCategories = subCategories; + } + _categories = categories; + _state = ResultState.HasData; + notifyListeners(); + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/certificate_provider.dart b/lib/providers/certificate_provider.dart new file mode 100644 index 0000000..249e696 --- /dev/null +++ b/lib/providers/certificate_provider.dart @@ -0,0 +1,181 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/certificate_model.dart'; +import 'package:initial_folder/models/check_certificate.model.dart'; +import 'package:initial_folder/models/my_certificate.dart'; +import 'package:initial_folder/services/all_certificate_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class CertificateProvider with ChangeNotifier { + final AllCertificateServices certificateServices; + CertificateProvider({required this.certificateServices}) { + getAllCertif(); + } + + bool _isChecked = true; + bool get isChecked => _isChecked; + + ResultState? _state; + + List<DataMyCertificateModel?>? _allCertificate; + + String? _userId; + String? _idPayment; + String? _certificateNo; + String? _username; + String? _title; + String? _idCourse; + int? _progress; + dynamic _finishDate; + int _allCertificateCount = 0; + bool _isSearch = false; + + List<DataMyCertificateModel?>? get allCertificate => _allCertificate; + + int? get allCertificateCount => _allCertificateCount; + dynamic get finishDate => _finishDate; + String? get idPayment => _idPayment; + String? get certificateNo => _certificateNo; + String? get name => _username; + String? get title => _title; + String? get userId => _userId; + String? get idCourse => _idCourse; + int? get progress => _progress; + bool get isSearch => _isSearch; + + ResultState? get state => _state; + + Future uploadCertificate(String idPayment, File pdfFile) async { + try { + _state = ResultState.Loading; + notifyListeners(); + await certificateServices.uploadCertificate(idPayment, pdfFile); + _state = ResultState.HasData; + notifyListeners(); + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + print( + "Error download sertifikat $e", + ); + } + } + + Future getCertif(String idCourse) async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CertificateModel> certif = + await certificateServices.getSertif(idCourse); + + if (certif.isEmpty) { + print("Jangan loading terus"); + _state = ResultState.NoData; + notifyListeners(); + } else { + _state = ResultState.HasData; + notifyListeners(); + _idPayment = certif[0].idPayment; + _certificateNo = certif[0].certificateNo; + _username = certif[0].name; + _title = certif[0].title; + _finishDate = certif[0].finishDate; + + notifyListeners(); + } + } catch (e) { + print("Ini gagal pas pindah ke certif cuy ${e}"); + } + } + + Future getAllCertif() async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<DataMyCertificateModel> allCertificate = + await AllCertificateServices().getAllCertificate(); + + allCertificate.sort((a, b) => b.progress!.compareTo(a.progress!)); + + _allCertificateCount = 0; + if (allCertificate.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + } else { + _state = ResultState.HasData; + _isSearch = false; + notifyListeners(); + _allCertificate = allCertificate; + for (var data in allCertificate) { + if (data.progress == 100) _allCertificateCount++; + } + notifyListeners(); + } + } catch (e) { + print("Jangan loading terus ${e}"); + } + } + + Future checkSertif(String text) async { + try { + _state = ResultState.Loading; + notifyListeners(); + CheckCertificateData certif = await certificateServices.checkSertif(text); + if (certif.certificateNo!.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + } else { + _state = ResultState.HasData; + _idCourse = certif.courseId; + _idPayment = certif.idPayment; + _certificateNo = certif.certificateNo; + _username = certif.name; + _title = certif.title; + _finishDate = certif.finishDate; + _progress = certif.progress; + notifyListeners(); + } + } catch (e) { + print("Ini error input no sertif${e}"); + } + } + + Future clearCheckSertif() async { + _idPayment = null; + } + + Future searchCertif(String keyword) async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<DataMyCertificateModel> allCertificate = + await AllCertificateServices().searchCertif(keyword); + _allCertificateCount = 0; + if (allCertificate.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + } else { + _state = ResultState.HasData; + _isSearch = true; + notifyListeners(); + _allCertificate = allCertificate; + for (var data in allCertificate) { + if (data.progress == 100) { + _allCertificateCount++; + } + } + notifyListeners(); + } + } catch (e) { + print(e); + } + } + + bool toggleCheckbox([bool value = false]) { + _isChecked = !_isChecked; + notifyListeners(); + return _isChecked; + } +} diff --git a/lib/providers/checkbox_provider.dart b/lib/providers/checkbox_provider.dart new file mode 100644 index 0000000..98a17de --- /dev/null +++ b/lib/providers/checkbox_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class CheckboxProvider with ChangeNotifier { + bool _isChecked = false; + + bool get isChecked => _isChecked; + + set isChecked(bool value) { + _isChecked = value; + notifyListeners(); + } +} diff --git a/lib/providers/counter_qna_comment_provider.dart b/lib/providers/counter_qna_comment_provider.dart new file mode 100644 index 0000000..50a8cb5 --- /dev/null +++ b/lib/providers/counter_qna_comment_provider.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/counter_qna_comment_model.dart'; +import 'package:initial_folder/services/qna_service.dart'; + +enum ResultState { loading, noData, hasData, error } +enum ResultStateLike { loading, error } + +class CounterQnaCommentProvider with ChangeNotifier { + final String idQna; + + CounterQnaCommentProvider({required this.idQna}) { + getCounterComment(idQna); + } + String _message = ''; + String get message => _message; + ResultState? _state; + ResultState? get state => _state; + CounterCommentModel? _counterCommentModel; + CounterCommentModel? get result => _counterCommentModel; + set counterComment(CounterCommentModel? counterCommentModel) { + _counterCommentModel = counterCommentModel; + notifyListeners(); + } + + //Get Counter Comment QNA + Future<dynamic> getCounterComment(String idQna) async { + try { + _state = ResultState.loading; + notifyListeners(); + CounterCommentModel counterCommentModel = + await QnaService().getCounterComment(idQna); + // print("Ini Cunter : ${counterCommentModel.data}"); + // ignore: unnecessary_null_comparison + if (counterCommentModel.data != null) { + print("ADA DATA"); + _state = ResultState.hasData; + notifyListeners(); + return _counterCommentModel = counterCommentModel; + } else { + print("TIDAK ADA DATA"); + _state = ResultState.noData; + notifyListeners(); + return _message = 'Tidak ada Data'; + } + } catch (e) { + _state = ResultState.error; + print(e); + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/counter_qna_like_provider.dart b/lib/providers/counter_qna_like_provider.dart new file mode 100644 index 0000000..9e8550f --- /dev/null +++ b/lib/providers/counter_qna_like_provider.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/counter_qna_like_model.dart'; +import 'package:initial_folder/services/qna_service.dart'; + +enum ResultState { loading, noData, hasData, error } +enum ResultStateLike { loading, error } + +class CounterQnaLikeProvider with ChangeNotifier { + final String idQna; + + CounterQnaLikeProvider({required this.idQna}) { + getLikeComment(idQna); + } + String _message = ''; + String get message => _message; + ResultState? _state; + ResultState? get state => _state; + CounterLikeModel? _counterLikeModel; + CounterLikeModel? get result => _counterLikeModel; + set counterComment(CounterLikeModel? counterLikeModel) { + _counterLikeModel = counterLikeModel; + notifyListeners(); + } + + //Get Counter Like QNA + Future<dynamic> getLikeComment(String idQna) async { + try { + _state = ResultState.loading; + notifyListeners(); + CounterLikeModel counterLikeModel = + await QnaService().getCounterLike(idQna); + // print("Ini Cunter : ${counterLikeModel.data}"); + // ignore: unnecessary_null_comparison + if (counterLikeModel.data != null) { + print("ADA DATA"); + _state = ResultState.hasData; + notifyListeners(); + return _counterLikeModel = counterLikeModel; + } else { + print("TIDAK ADA DATA"); + _state = ResultState.noData; + notifyListeners(); + return _message = 'Tidak ada Data'; + } + } catch (e) { + _state = ResultState.error; + print(e); + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/coupon_course_provider.dart b/lib/providers/coupon_course_provider.dart new file mode 100644 index 0000000..f262586 --- /dev/null +++ b/lib/providers/coupon_course_provider.dart @@ -0,0 +1,53 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/services/coupon_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class CouponCourseProvider with ChangeNotifier { + final CouponService couponService; + String coupon; + CouponCourseProvider({required this.couponService, required this.coupon}) { + getDiscountCourse(coupon); + } + dynamic _data; + + ResultState? _state; + + String _message = ''; + + dynamic get result => _data; + + ResultState? get state => _state; + + String get message => _message; + + set data(dynamic data) { + _data = data; + notifyListeners(); + } + + Future<dynamic> getDiscountCourse(coupon) async { + try { + _state = ResultState.Loading; + notifyListeners(); + dynamic data = await couponService.getDiscountCourse(coupon); + if (data.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + print("ini berhasil provider kupon ${_data}"); + notifyListeners(); + return _data = data; + } + } catch (e) { + _state = ResultState.Error; + print("Gagal kupon ${e}"); + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/course_by_category_provider.dart b/lib/providers/course_by_category_provider.dart new file mode 100644 index 0000000..703cb64 --- /dev/null +++ b/lib/providers/course_by_category_provider.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/services/course_by_category_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class CourseByCategoryProvider with ChangeNotifier { + final CourseByCategoryService courseByCategoryService; + final String id; + final String subId; + final bool fetchBySubcategory; + CourseByCategoryProvider({ + required this.courseByCategoryService, + required this.id, + required this.subId, + this.fetchBySubcategory = false, + }) { + if (fetchBySubcategory) { + getCourseByCategory(id, subId); + } else { + getCourseOnlyCategory(id); + } + } + List<CourseModel> _courseByCategory = []; + + ResultState? _state; + + String _message = ''; + + List<CourseModel> get result => _courseByCategory; + + ResultState? get state => _state; + + String get message => _message; + + set courseByCategory(List<CourseModel> courseByCategory) { + _courseByCategory = courseByCategory; + notifyListeners(); + } + + Future<dynamic> getCourseByCategory(_id, subId) async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CourseModel> courseByCategory = + await courseByCategoryService.getCourseCategoryAndSub(_id, subId); + if (courseByCategory.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + print(_courseByCategory); + notifyListeners(); + return _courseByCategory = courseByCategory; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + Future<dynamic> getCourseOnlyCategory(_id) async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CourseModel> courseByCategory = + await courseByCategoryService.getCourseOnlyCategory(_id); + if (courseByCategory.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _courseByCategory = courseByCategory; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/current_lesson_provider.dart b/lib/providers/current_lesson_provider.dart new file mode 100644 index 0000000..7b500d6 --- /dev/null +++ b/lib/providers/current_lesson_provider.dart @@ -0,0 +1,27 @@ +import 'package:flutter/foundation.dart'; +import 'package:initial_folder/models/lesson_model.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/services/current_lesson_service.dart'; + +class CurrentLessonProvider extends ChangeNotifier { + late LessonModel _currentLesson; + LessonModel get payload => _currentLesson; + + ResultState _state = ResultState.loading; + ResultState get state => _state; + + Future<dynamic> fetchCurrentLesson(String lessonId) async { + _state = ResultState.loading; + notifyListeners(); + + try { + final data = await CurrentLessonService().getCurrentLesson(lessonId); + _currentLesson = data; + _state = ResultState.hasData; + notifyListeners(); + } catch (e) { + _state = ResultState.error; + notifyListeners(); + } + } +} diff --git a/lib/providers/data_diri_provider.dart b/lib/providers/data_diri_provider.dart new file mode 100644 index 0000000..b51b9e1 --- /dev/null +++ b/lib/providers/data_diri_provider.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/data_diri_model.dart'; +import 'package:initial_folder/services/user_info_service.dart'; + +enum ResultStateData { Loading, NoData, HasData, Error } + +class DataDiriProvider with ChangeNotifier { + final UserInfoService userInfoService; + + DataDiriProvider({ + required this.userInfoService, + }) { + getDataDiri(); + } + + DataDiriModel? _dataDiriModel; + + ResultStateData? _state; + + String _message = ''; + + DataDiriModel? get result => _dataDiriModel; + + ResultStateData? get state => _state; + + String get message => _message; + + bool _isNewBirthDate = false; + bool get isNewBirthDate => _isNewBirthDate; + + bool _newName = false; + bool get newName => _newName; + + bool _newHeadline = false; + bool get newHeadline => _newHeadline; + + bool _newBiograpy = false; + bool get newBiograpy => _newBiograpy; + + bool _newEmail = false; + bool get newEmail => _newEmail; + + bool _newPhone = false; + bool get newPhone => _newPhone; + + bool _newGender = false; + bool get newGender => _newGender; + + bool _newInstagram = false; + bool get newInstagram => _newInstagram; + + bool _newTwitter = false; + bool get newTwitter => _newTwitter; + + bool _newFacebook = false; + bool get newFacebook => _newFacebook; + + bool _newLinkedin = false; + bool get newLinkedin => _newLinkedin; + + set isNewBirthDate(bool value) { + _isNewBirthDate = value; + notifyListeners(); + } + + set newName(bool value) { + _newName = value; + notifyListeners(); + } + + set newHeadline(bool value) { + _newHeadline = value; + notifyListeners(); + } + + set newBiograpy(bool value) { + _newBiograpy = value; + notifyListeners(); + } + + set newEmail(bool value) { + _newEmail = value; + notifyListeners(); + } + + set newPhone(bool value) { + _newPhone = value; + notifyListeners(); + } + + set newGender(bool value) { + _newGender = value; + notifyListeners(); + } + + set newInstagram(bool value) { + _newInstagram = value; + notifyListeners(); + } + + set newTwitter(bool value) { + _newTwitter = value; + notifyListeners(); + } + + set newFacebook(bool value) { + _newFacebook = value; + notifyListeners(); + } + + set newLinkedin(bool value) { + _newLinkedin = value; + notifyListeners(); + } + + set dataDiri(DataDiriModel dataDiri) { + _dataDiriModel = dataDiri; + notifyListeners(); + } + + Future<dynamic> getDataDiri() async { + try { + _state = ResultStateData.Loading; + notifyListeners(); + DataDiriModel dataDiri = await userInfoService.getDataDiri(); + + _state = ResultStateData.HasData; + notifyListeners(); + _dataDiriModel = dataDiri; + return _dataDiriModel; + } catch (e) { + _state = ResultStateData.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/description_provider.dart b/lib/providers/description_provider.dart new file mode 100644 index 0000000..b2d2180 --- /dev/null +++ b/lib/providers/description_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class DescriptionProvider with ChangeNotifier { + bool isExpanded; + + DescriptionProvider({this.isExpanded = true}); + + void expanded() { + isExpanded = !isExpanded; + notifyListeners(); + } +} diff --git a/lib/providers/detail_course_coupon_provider.dart b/lib/providers/detail_course_coupon_provider.dart new file mode 100644 index 0000000..7e5d990 --- /dev/null +++ b/lib/providers/detail_course_coupon_provider.dart @@ -0,0 +1,87 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:rxdart/rxdart.dart'; + +enum ResultState { Loading, HasData, Error, NoData } + +class DetailCourseCouponProvider with ChangeNotifier { + final CourseService courseService; + final String id; + final String coupon; + DetailCourseCouponProvider( + {required this.courseService, required this.id, required this.coupon}) { + getDetailCourseCoupon(id, coupon); + } + + DataDetailCourseModel? _detailCourse; + + DataDetailCourseModel? get result => _detailCourse; + + ResultState? _state; + + String _message = ''; + + ResultState? get state => _state; + + String get message => _message; + + set detailCourse(DataDetailCourseModel? detail) { + _detailCourse = detail; + notifyListeners(); + } + + Future<dynamic> getDetailCourseCoupon(_id, String coupon) async { + try { + _state = ResultState.Loading; + notifyListeners(); + DataDetailCourseModel? detail = + await courseService.getDetailCourseCoupon(_id, coupon); + + _state = ResultState.HasData; + print("Berhasil detail kupon di provider ${_detailCourse}"); + notifyListeners(); + return _detailCourse = detail; + } catch (e) { + print("Gagal detail kupon di provider ${e}"); + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} + +class DetailCouponProvider with ChangeNotifier { + DataDetailCourseModel? _detailCourse; + + DataDetailCourseModel? get result => _detailCourse; + + StreamController<DataDetailCourseModel> streamController = BehaviorSubject(); + + String _message = ''; + + String get message => _message; + + set detailCourse(DataDetailCourseModel? detail) { + _detailCourse = detail; + notifyListeners(); + } + + Stream<DataDetailCourseModel> get detailStream { + return streamController.stream; + } + + Future<void> getDetail(id, coupon) async { + try { + print("Berhasil detail kupon di provider"); + DataDetailCourseModel? detail = + await CourseService().getDetailCourseCoupon(id, coupon); + streamController.add(detail); + } catch (e) { + print("Gagal detail kupon di provider 2 ${e}"); + } + } +} diff --git a/lib/providers/detail_course_provider.dart b/lib/providers/detail_course_provider.dart new file mode 100644 index 0000000..e2ba096 --- /dev/null +++ b/lib/providers/detail_course_provider.dart @@ -0,0 +1,109 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:rxdart/rxdart.dart'; + +enum ResultState { Loading, HasData, Error, NoData } + +class DetailCourseProvider with ChangeNotifier { + final CourseService courseService; + final String id; + DetailCourseProvider({required this.courseService, required this.id}) { + Condition.loginEmail ? getDetailCourseLogin(id) : getDetailCourse(id); + } + + DetailCourseModel? _detailCourse; + DetailCourseModel? get result => _detailCourse; + + ResultState? _state; + ResultState? get state => _state; + + String _message = ''; + String get message => _message; + int? get checkoutPrice => _detailCourse?.data[0][0].checkoutPrice; + + set detailCourse(DetailCourseModel? detail) { + _detailCourse = detail; + notifyListeners(); + } + + Future<dynamic> getDetailCourse(_id) async { + try { + _state = ResultState.Loading; + notifyListeners(); + DetailCourseModel? detail = await courseService.getDetailCourse(_id); + + if (detail.data[0].isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Tidak ada data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _detailCourse = detail; + } + } catch (e) { + _state = ResultState.Error; + print(e); + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + Future<dynamic> getDetailCourseLogin(id) async { + try { + _state = ResultState.Loading; + notifyListeners(); + DetailCourseModel? detail = await courseService.getDetailCourseLogin(id); + + if (detail.data[0].isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Tidak ada data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _detailCourse = detail; + } + } catch (e) { + _state = ResultState.Error; + print(e); + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} + +class DetailProvider with ChangeNotifier { + DetailCourseModel? _detailCourse; + DetailCourseModel? get result => _detailCourse; + + StreamController<DetailCourseModel> streamController = BehaviorSubject(); + + String _message = ''; + String get message => _message; + int? get checkoutPrice => _detailCourse?.data[0][0].checkoutPrice ?? 0; + + set detailCourse(DetailCourseModel? detail) { + _detailCourse = detail; + notifyListeners(); + } + + Stream<DetailCourseModel> get detailStream { + return streamController.stream; + } + + Future<void> getDetail(id) async { + try { + DetailCourseModel? detail = + await CourseService().getDetailCourseLogin(id); + streamController.add(detail); + } catch (e) { + log(e as String); + } + } +} diff --git a/lib/providers/detail_invoice_provider.dart b/lib/providers/detail_invoice_provider.dart new file mode 100644 index 0000000..4bedee1 --- /dev/null +++ b/lib/providers/detail_invoice_provider.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/detail_invoice_model.dart'; +import 'package:initial_folder/services/detail_invoice_service.dart'; +import 'package:provider/provider.dart'; + +enum ResultState { loading, noData, hasData, error } + +class DetailInvoiceProvider extends ChangeNotifier { + final DetailInvoiceService _detailInvoiceService = DetailInvoiceService(); + + List<DataDetailInvoiceModel>? _detailInvoice; + String? _message; + String? _thumbnail; + ResultState? _state; + + String? get message => _message; + String? get thumbnail => _thumbnail; + List<DataDetailInvoiceModel>? get detailInvoice => _detailInvoice; + ResultState? get state => _state; + + set selectedThumbnail(String? value) { + _thumbnail = value; + notifyListeners(); + } + + Future<dynamic> fetchDetailInvoice(String? orderId) async { + try { + _state = ResultState.loading; + notifyListeners(); + final data = await _detailInvoiceService.detailInvoice(orderId); + if (data.isEmpty) { + _state = ResultState.noData; + _message = 'Invoice Detail is Empty'; + } else { + _state = ResultState.hasData; + _detailInvoice = data; + } + } catch (e) { + _state = ResultState.error; + _message = 'Error -> $e'; + } + notifyListeners(); + } +} diff --git a/lib/providers/detail_rating_course_provider.dart b/lib/providers/detail_rating_course_provider.dart new file mode 100644 index 0000000..c27493c --- /dev/null +++ b/lib/providers/detail_rating_course_provider.dart @@ -0,0 +1,79 @@ +import 'package:flutter/foundation.dart'; +import 'package:initial_folder/models/detail_rating_course_model.dart'; +import 'package:initial_folder/services/course_service.dart'; + +enum ResultState { Loading, HasData, Error, NoData } + +class DetailRatingCourseProvider with ChangeNotifier { + final CourseService courseService; + final String id; + DetailRatingCourseProvider({required this.courseService, required this.id}) { + getDetailCourse(id); + } + + RatingCourseDetailModel? _detailRatingCourse; + + RatingCourseDetailModel? get result => _detailRatingCourse; + List<DataReview>? _review; + List<DataReview>? get review => _review; + ResultState? _state; + + String _message = ''; + + ResultState? get state => _state; + + String get message => _message; + + set detailCourse(RatingCourseDetailModel detail) { + _detailRatingCourse = detail; + notifyListeners(); + } + + int _currentIndex = 0; + + int get currentIndex => _currentIndex; + + set currentIndex(int index) { + _currentIndex = index; + notifyListeners(); + } + + Future<dynamic> getDetailCourse([_id]) async { + try { + _state = ResultState.Loading; + notifyListeners(); + RatingCourseDetailModel detail = + await courseService.getRatingDetailCourse((_id != null) ? _id : id ); + + _state = ResultState.HasData; + _review = detail.dataReview; + notifyListeners(); + return _detailRatingCourse = detail; + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + Future<dynamic> filterCourse(int rating) async { + try { + _state = ResultState.Loading; + notifyListeners(); + RatingCourseDetailModel detail = + await courseService.getRatingDetailCourse(id); + + _state = ResultState.HasData; + _review = detail.dataReview.where((element) => element.rating == rating.toString()).toList(); + notifyListeners(); + + return _detailRatingCourse = detail; + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + +} diff --git a/lib/providers/email_provider.dart b/lib/providers/email_provider.dart new file mode 100644 index 0000000..c0263bb --- /dev/null +++ b/lib/providers/email_provider.dart @@ -0,0 +1,19 @@ +// import 'package:flutter/material.dart'; + +// class EmailProvider with ChangeNotifier { +// String? _currentEmail = ''; +// String? _currentName = ''; + +// String? get currentEmail => _currentEmail; +// String? get currentName => _currentName; + +// set currentEmail(String? email) { +// _currentEmail = email; +// notifyListeners(); +// } + +// set currentName(String? name) { +// _currentName = name; +// notifyListeners(); +// } +// } diff --git a/lib/providers/filters_course_provider.dart b/lib/providers/filters_course_provider.dart new file mode 100644 index 0000000..e20acd5 --- /dev/null +++ b/lib/providers/filters_course_provider.dart @@ -0,0 +1,133 @@ +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/services/search_service.dart'; + +enum ResultState { loading, noData, hasData, error } + +class FilterCourseProvider with ChangeNotifier { + final SearchService searchService; + FilterCourseProvider({required this.searchService}); + + List<CourseModel> _filteredCourse = []; + ResultState? _state; + String _message = ''; + + List<CourseModel> get filterResult => [..._filteredCourse]; + ResultState? get state => _state; + String get message => _message; + + List<String> _categories = ['']; + List<String> get categories => [..._categories]; + void addCategory(String id) { + _categories.add(id); + notifyListeners(); + } + + void removeCategory(String id) { + _categories.removeWhere((item) => item == id); + notifyListeners(); + } + + void allCategories() { + _categories = []; + notifyListeners(); + } + + List<String> _levels = ['']; + List<String> get levels => [..._levels]; + void addLevel(String level) { + _levels.add(level); + notifyListeners(); + } + + void removeLevel(String level) { + _levels.removeWhere((item) => item == level); + notifyListeners(); + } + + void allLevel() { + _levels = []; + } + + String _currentIndexRating = ''; + String get currentIndexRating => _currentIndexRating; + set currentIndexRating(String index) { + _currentIndexRating = index; + notifyListeners(); + } + + String _currentIndexPrice = ''; + String get currentIndexPrice => _currentIndexPrice; + set currentIndexPrice(String index) { + _currentIndexPrice = index; + notifyListeners(); + } + + String _currentIndexLevelCheckBox = ''; + String get currentIndexLevelCheckBox => _currentIndexLevelCheckBox; + set currentIndexLevelCheckBox(String index) { + _currentIndexLevelCheckBox = index; + notifyListeners(); + } + + void resetFilter() { + _currentIndexPrice = ''; + _currentIndexRating = ''; + _categories = ['']; + _levels = ['']; + _filteredCourse = []; + } + + void resetState() { + _state = null; + } + + bool _isSearch = false; + bool get isSearch => _isSearch; + void isSearchsTrue() { + _isSearch = true; + notifyListeners(); + } + + void isSearchsFalse() { + _isSearch = false; + notifyListeners(); + } + + Future<dynamic> filter({ + String category = '', + String price = '', + String level = '', + String language = '', + String rating = '', + String keyword = '', + }) async { + try { + _state = ResultState.loading; + notifyListeners(); + List<CourseModel> course = await searchService.filter( + price: price, + level: level, + language: language, + rating: rating, + keyword: keyword, + ); + print("Ini keyword filter search ${keyword}"); + + if (course.isEmpty) { + _state = ResultState.noData; + notifyListeners(); + return _message = 'No Data'; + } else { + _state = ResultState.hasData; + notifyListeners(); + return _filteredCourse = course; + } + } catch (e) { + _state = ResultState.error; + print(e); + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/firebase_authentication_provider.dart b/lib/providers/firebase_authentication_provider.dart new file mode 100644 index 0000000..840d680 --- /dev/null +++ b/lib/providers/firebase_authentication_provider.dart @@ -0,0 +1,161 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +// import 'package:flutter_facebook_auth/flutter_facebook_auth.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/user_model.dart'; +import 'package:initial_folder/services/auth_service.dart'; + +class FirebaseAuthenticationProvider extends ChangeNotifier { + final googleSignIn = GoogleSignIn(); + GoogleSignInAccount? _user; + GoogleSignInAccount get user => _user!; + + UserModel? _userModel; + UserModel? get userModel => _userModel; + + set userModel(UserModel? userModel) { + _userModel = userModel; + notifyListeners(); + } + + Future googleSignUp() async { + final googleUser = await googleSignIn.signIn(); + if (googleUser == null) return; + _user = googleUser; + final googleAuth = await googleUser.authentication; + final credential = + GoogleAuthProvider.credential(accessToken: googleAuth.idToken); + await FirebaseAuth.instance.signInWithCredential(credential); + await AuthService().googleSignInAuth( + idToken: googleAuth.idToken ?? '', + // email: googleUser.email, + // password: '', + ); + // _userModel = userModel; + await UsersInfo().setEmail(googleUser.email); + notifyListeners(); + return true; + } + + Future<bool> googleLogin() async { + try { + print('Preparing'); + final googleUser = await googleSignIn.signIn(); + print('Start'); + if (googleUser == null) { + print('Failed'); + await logout(); + return false; + } + + print('State 1'); + final GoogleSignInAuthentication googleAuth = + await googleUser.authentication; + + final credential = GoogleAuthProvider.credential( + accessToken: googleAuth.accessToken, + idToken: googleAuth.idToken, + ); + + print('State 2'); + final userCredential = + await FirebaseAuth.instance.signInWithCredential(credential); + + print("ID Token: ${googleAuth.idToken}"); + + await AuthService().googleSignInAuth( + idToken: googleAuth.idToken ?? '', + ); + // _userModel = userModel; + + print('State 3'); + await UsersInfo().setEmail(googleUser.email); + + print('Login sukses'); + notifyListeners(); + return true; + } catch (e) { + // print(e); + // return false; + if (e.toString() == 'Exception: Akun anda belum terdaftar') { + await logout(); + throw Exception('Akun anda belum terdaftar'); + } else { + print(e); + await logout(); + return false; + } + } + } + + // Future facebookLogin() async { + // try { + // final LoginResult facebookSignIn = await FacebookAuth.instance.login(); + // if (facebookSignIn.accessToken == null) { + // await logout(); + // return false; + // } + // switch (facebookSignIn.status) { + // case LoginStatus.success: + // try { + // final facebookAuthCredential = FacebookAuthProvider.credential( + // facebookSignIn.accessToken!.token); + // await FirebaseAuth.instance + // .signInWithCredential(facebookAuthCredential); + + // String name = await FacebookAuth.instance + // .getUserData() + // .then((data) => data['name']); + // String email = await FacebookAuth.instance + // .getUserData() + // .then((data) => data['email']); + // String pictureUrl = await FacebookAuth.instance + // .getUserData() + // .then((data) => data['picture']['data']['url']); + + // UserModel userModel = await AuthService().facebookSignInAuth( + // name: name, + // email: email, + // password: '', + // pictureUrl: pictureUrl, + // ); + // _userModel = userModel; + + // await UsersInfo().setEmail(email); + + // notifyListeners(); + // return true; + // } catch (e) { + // await logout(); + // return false; + // } + // case LoginStatus.cancelled: + // await logout(); + // return false; + // case LoginStatus.failed: + // await logout(); + // return false; + // default: + // await logout(); + // return false; + // } + // } catch (e) { + // await logout(); + // return false; + // } + // } + + Future logout() async { + try { + if (googleSignIn.currentUser != null) { + await googleSignIn.disconnect(); + } // else if (await FacebookAuth.instance.accessToken != null) { + // await FacebookAuth.instance.logOut(); + // } + } finally { + FirebaseAuth.instance.signOut(); + notifyListeners(); + } + } +} diff --git a/lib/providers/forgot_password_provider.dart b/lib/providers/forgot_password_provider.dart new file mode 100644 index 0000000..c09314f --- /dev/null +++ b/lib/providers/forgot_password_provider.dart @@ -0,0 +1,19 @@ +import 'package:initial_folder/services/forgot_password_service.dart'; + +import '../models/forgot_password_model.dart'; +import 'package:flutter/foundation.dart'; + +class ForgotPasswordProvider extends ChangeNotifier { + final ForgotService _apiService = ForgotService(); + ForgotPasswordModel? _forgotPasswordModel; + ForgotPasswordModel? get forgotPasswordModel => _forgotPasswordModel; + + Future<void> forgotPassword(String email) async { + try { + _forgotPasswordModel = await _apiService.forgotPassword(email: email); + notifyListeners(); + } catch (e) { + rethrow; + } + } +} diff --git a/lib/providers/history_transactions_provider.dart b/lib/providers/history_transactions_provider.dart new file mode 100644 index 0000000..bdb18f4 --- /dev/null +++ b/lib/providers/history_transactions_provider.dart @@ -0,0 +1,84 @@ +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/services/history_transactions_service.dart'; + +enum ResultState { loading, noData, hasData, error } + +class HistoryTranscationsProvider with ChangeNotifier { + HistoryTranscationsProvider({required this.historyTransactionService}) { + getHistoryTransaction(); + } + + final HistoryTransactionService historyTransactionService; + + ResultState? _state; + String _message = ''; + List<HistoryTransactionModel>? _historyTransactionsModel; + List<HistoryTransactionModel>? get historyPayment { + return _historyTransactionsModel == null + ? [] + : _historyTransactionsModel! + .where((element) => element.statusPayment != '2') + .toList(); + } + + List<HistoryTransactionModel>? get paymentPending { + return _historyTransactionsModel == null + ? [] + : _historyTransactionsModel! + .where((element) => element.statusPayment == '2') + .toList(); + } + + // transaksi dengan status "-5" + List<HistoryTransactionModel>? get paymentAwaitingMethod { + return _historyTransactionsModel == null + ? [] + : _historyTransactionsModel! + .where((element) => element.statusPayment == '-5') + .toList(); + } + + ResultState? get state => _state; + String get message => _message; + + set histtoryTranscation(List<HistoryTransactionModel> historyTr) { + _historyTransactionsModel = historyTr; + notifyListeners(); + } + + Stream<List<HistoryTransactionModel>> getHistoryTransactionStream() async* { + while (true) { + List<HistoryTransactionModel>? response = + await historyTransactionService.historyTransactions(); + + if (response.isNotEmpty) { + yield response + .where((transaction) => transaction.statusPayment == '2') + .toList(); + } + } + } + + Future<dynamic> getHistoryTransaction() async { + try { + _state = ResultState.loading; + notifyListeners(); + List<HistoryTransactionModel>? response = + await historyTransactionService.historyTransactions(); + if (response.isEmpty) { + _state = ResultState.noData; + notifyListeners(); + return _message = 'Tidak Ada data'; + } else { + _state = ResultState.hasData; + notifyListeners(); + return _historyTransactionsModel = response; + } + } catch (e) { + _state = ResultState.error; + notifyListeners(); + return _message = 'Data transaksi gagal diambil'; + } + } +} diff --git a/lib/providers/incomplete_profile_provider.dart b/lib/providers/incomplete_profile_provider.dart new file mode 100644 index 0000000..6c74072 --- /dev/null +++ b/lib/providers/incomplete_profile_provider.dart @@ -0,0 +1,93 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/update_incomplete_profile_model.dart'; +import 'package:initial_folder/models/user_info_incomplete_model.dart'; +import 'package:initial_folder/services/user_info_service.dart'; + +enum ResultState { Loading, Success, NoData, HasData, Error } + +class IncompleteProfileProvider with ChangeNotifier { + final UserInfoService userInfoService; + + IncompleteProfileProvider({ + required this.userInfoService, + }); + + UpdateIncompleteProfileModel? _updateIncompleteProfileModel; + UserInfoIncompleteModel? _userInfoIncompleteModel; + ResultState? _state; + bool? _isUserInfoComplete; + String _message = ''; + + UpdateIncompleteProfileModel? get updateIncompleteProfileModel => + _updateIncompleteProfileModel; + UserInfoIncompleteModel? get userInfoIncompleteModel => + _userInfoIncompleteModel; + ResultState? get state => _state; + bool? get isUserInfoComplete => _isUserInfoComplete; + String get message => _message; + + Future<bool> getUserInfoIncomplete() async { + try { + _state = ResultState.Loading; + notifyListeners(); + UserInfoIncompleteModel userInfoIncomplete = + await userInfoService.getUserInfoIncomplete(); + + if (userInfoIncomplete.data == null) { + _state = ResultState.NoData; + _isUserInfoComplete = null; + notifyListeners(); + return false; + } else { + if (userInfoIncomplete.status == 202) { + _isUserInfoComplete = false; + } else if (userInfoIncomplete.status == 200) { + _isUserInfoComplete = true; + } + notifyListeners(); + _userInfoIncompleteModel = userInfoIncomplete; + return true; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + _message = 'Error --> $e'; + _userInfoIncompleteModel = null; + return false; + } + } + + Future<bool> updateIncompleteProfile({ + String? fullname, + String? phone, + String? email, + String? newPassword, + String? newConfirmPassword, + }) async { + try { + _state = ResultState.Loading; + notifyListeners(); + _updateIncompleteProfileModel = + await UserInfoService().updateIncompleteProfile( + fullname: fullname, + phone: phone, + email: email, + newPassword: newPassword, + newConfirmPassword: newConfirmPassword, + ); + if (_updateIncompleteProfileModel != null) { + if (_updateIncompleteProfileModel!.status == 200) { + _state = ResultState.Success; + notifyListeners(); + return true; + } + } + + return false; + } catch (e) { + print("Exception: $e"); + _updateIncompleteProfileModel = null; + return false; + } + } +} diff --git a/lib/providers/instructor_provider.dart b/lib/providers/instructor_provider.dart new file mode 100644 index 0000000..4544e71 --- /dev/null +++ b/lib/providers/instructor_provider.dart @@ -0,0 +1,45 @@ +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/instructor_model.dart'; +import 'package:initial_folder/services/instructor_service.dart'; + +enum ResultState { Loading, HasData, NoData, Error } + +class InstructorProvider with ChangeNotifier { + final InstructorService instructorService; + final int id; + InstructorProvider({required this.instructorService, required this.id}) { + getProfileInstructor(id); + } + InstructorModel? _instructorModel; + ResultState? _state; + String _message = ''; + InstructorModel? get result => _instructorModel; + ResultState? get state => _state; + String get message => _message; + set instruktur(InstructorModel instruktur) { + _instructorModel = instruktur; + notifyListeners(); + } + + Future<dynamic> getProfileInstructor(_id) async { + try { + _state = ResultState.Loading; + notifyListeners(); + InstructorModel instruktur = + await instructorService.getInstructorProfile(_id); + if (instruktur.data.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Tidak Ada Data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _instructorModel = instruktur; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/latest_course_provider.dart b/lib/providers/latest_course_provider.dart new file mode 100644 index 0000000..291462c --- /dev/null +++ b/lib/providers/latest_course_provider.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/services/course_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class LatestCourseProvider with ChangeNotifier { + final CourseService courseService; + LatestCourseProvider({required this.courseService}) { + getLatestCourse(); + } + + List<CourseModel> _course = []; + + ResultState? _state; + + String _message = ''; + + List<CourseModel> get result => _course; + + ResultState? get state => _state; + + String get message => _message; + + set course(List<CourseModel> course) { + _course = course; + notifyListeners(); + } + + Future<dynamic> getLatestCourse() async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CourseModel> course = await courseService.getLatestCourse(); + if (course.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _course = course; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/lesson_course_provider.dart b/lib/providers/lesson_course_provider.dart new file mode 100644 index 0000000..c69b1e1 --- /dev/null +++ b/lib/providers/lesson_course_provider.dart @@ -0,0 +1,109 @@ +import 'dart:developer'; + +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/lesson_course_model.dart'; +import 'package:initial_folder/models/section_model.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; + +enum ResultState { loading, hasData, noData, error } +// enum ResultStateUpdate { uninitialized, loading, success, eror } + +class LessonCourseProvider with ChangeNotifier { + final LessonCourseService lessonCourseService; + final int id; + LessonCourseProvider({required this.lessonCourseService, required this.id}) { + getLessonCourse(id); + } + + bool switchbutton = false; + + LessonCourseModel? _lessonCourseModel; + SectionModel? _sectionModel; + ResultState? _state; + // ResultStateUpdate _stateUpdate = ResultStateUpdate.uninitialized; + String _message = ''; + + LessonCourseModel? get result => _lessonCourseModel; + SectionModel? get sectionResult => _sectionModel; + + ResultState? get state => _state; + // ResultStateUpdate get stateUpdate => _stateUpdate; + String get message => _message; + + String _url = ''; + + String get url => _url; + + set uri(String urlVideo) { + _url = urlVideo; + notifyListeners(); + } + + indexUri(String indexUri) { + _url = indexUri; + notifyListeners(); + } + + set lessonCourse(LessonCourseModel lesson) { + _lessonCourseModel = lesson; + notifyListeners(); + } + + set sectionCourse(SectionModel section) { + _sectionModel = section; + notifyListeners(); + } + // set newMap(NewMap lesson) { + // _newMap = lesson; + // notifyListeners(); + // } + + void autoplay() { + switchbutton = !switchbutton; + notifyListeners(); + } + + Future<dynamic> getLessonCourse(int _id) async { + try { + _state = ResultState.loading; + notifyListeners(); + LessonCourseModel lesson = await lessonCourseService.getLessonCourse(_id); + SectionModel section = await lessonCourseService.getSectionCourse(_id); + + if (lesson.data[0].isEmpty && section.data[0].isEmpty) { + _state = ResultState.noData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.hasData; + + notifyListeners(); + _sectionModel = section; + return _lessonCourseModel = lesson; + } + } catch (e) { + _state = ResultState.error; + notifyListeners(); + print('masuk sini'); + return _message = 'Error --> $e'; + } + } + + Future<bool> updateLessonCourse(String _courseId, String _lessonId) async { + try { + // _stateUpdate = ResultStateUpdate.loading; + // notifyListeners(); + bool update = await lessonCourseService.updateLessonCourse(_lessonId); + + if (update) { + // _stateUpdate = ResultStateUpdate.success; + // notifyListeners(); + return true; + } + return false; + } catch (e) { + // _stateUpdate = ResultStateUpdate.eror; + return false; + } + } +} diff --git a/lib/providers/like_announcement.dart b/lib/providers/like_announcement.dart new file mode 100644 index 0000000..87d4a74 --- /dev/null +++ b/lib/providers/like_announcement.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/services/announcement_service.dart'; + +enum ResultState { uninitilized, loading, failed, success } + +class LikeOrAnnouncementProvider with ChangeNotifier { + ResultState _state = ResultState.uninitilized; + + ResultState get state => _state; + + Future<bool> likeOrAnnouncement(String tokenAnnouncement) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = + await AnnouncementService().likeAnnouncement(tokenAnnouncement); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/like_or_unlike_provider.dart b/lib/providers/like_or_unlike_provider.dart new file mode 100644 index 0000000..25c4205 --- /dev/null +++ b/lib/providers/like_or_unlike_provider.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/services/qna_service.dart'; + +enum ResultState { uninitilized, loading, failed, success } + +class LikeOrUnlikeProvider with ChangeNotifier { + ResultState _state = ResultState.uninitilized; + + ResultState get state => _state; + //Posting QNA + Future<bool> likeOrUnlike(int idQna) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await QnaService().likeOrLike(idQna); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/login_provider.dart b/lib/providers/login_provider.dart new file mode 100644 index 0000000..0d40019 --- /dev/null +++ b/lib/providers/login_provider.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +class LoginProvider with ChangeNotifier { + bool _loadGoogle = false; + bool _loadFacebook = false; + bool get loadGoogle => _loadGoogle; + bool get loadFacebook => _loadFacebook; + + loadGoogleActive(bool value) { + _loadGoogle = value; + notifyListeners(); + } + + loadFacebookActive(bool value) { + _loadFacebook= value; + notifyListeners(); + } +} diff --git a/lib/providers/metode_provider.dart b/lib/providers/metode_provider.dart new file mode 100644 index 0000000..94de409 --- /dev/null +++ b/lib/providers/metode_provider.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +enum MetodePembayaran { kreditdebit, mandiri, bni, permata, lainnya, gopay } + +class MetodeProvider with ChangeNotifier { + MetodePembayaran? _character = MetodePembayaran.kreditdebit; + + MetodePembayaran? get character => _character; + + set character(MetodePembayaran? value) { + _character = value; + notifyListeners(); + } +} diff --git a/lib/providers/my_course_provider.dart b/lib/providers/my_course_provider.dart new file mode 100644 index 0000000..a3d5d47 --- /dev/null +++ b/lib/providers/my_course_provider.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/my_course_model.dart'; +import 'package:initial_folder/services/course_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +enum SearchResultState { Loading, NoData, HasData, Error } + +class MyCourseProvider with ChangeNotifier { + final CourseService courseService; + MyCourseProvider({required this.courseService}) { + getMyCourse(); + } + MyCourseModel? _myCourseModel; + MyCourseModel? _searchCourseModel; + ResultState? _state; + SearchResultState? _searchResultState; + String _message = ''; + + MyCourseModel? get result => _myCourseModel; + MyCourseModel? get searchResult => _searchCourseModel; + ResultState? get state => _state; + SearchResultState? get searchResultState => _searchResultState; + + String get message => _message; + + set myCourse(MyCourseModel myCourse) { + _myCourseModel = myCourse; + notifyListeners(); + } + + Future<dynamic> getMyCourse() async { + try { + _state = ResultState.Loading; + notifyListeners(); + MyCourseModel myCourse = await courseService.getMyCourse(); + + if (myCourse.data[0].isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _myCourseModel = myCourse; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + print('resultstate mycourse eror'); + return _message = 'Error --> $e'; + } + } + + Future<dynamic> getSearchMyCourse(String courseName) async { + try { + _searchResultState = SearchResultState.Loading; + + notifyListeners(); + MyCourseModel myCourse = + await courseService.getSearchMyCourse(courseName); + if (myCourse.data[0].isEmpty) { + _searchResultState = SearchResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _searchResultState = SearchResultState.HasData; + notifyListeners(); + + return _searchCourseModel = myCourse; + } + } catch (e) { + _searchResultState = SearchResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + clearSearch() { + _searchResultState = null; + Future.delayed(Duration(seconds: 2), () { + if (_searchCourseModel != null && _searchCourseModel!.data.isNotEmpty) { + _searchCourseModel!.data[0].clear(); + } + }); + } +} diff --git a/lib/providers/notification_provider.dart b/lib/providers/notification_provider.dart new file mode 100644 index 0000000..f5e9621 --- /dev/null +++ b/lib/providers/notification_provider.dart @@ -0,0 +1,127 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:initial_folder/models/notification.dart'; +import 'package:initial_folder/services/notification_service.dart'; + +enum resultState { Loading, NoData, HasData, Error } + +enum countState { Loading, NoData, HasData, Error } + +class NotificationProvider with ChangeNotifier { + final NotificationServices notificationServices; + NotificationProvider({required this.notificationServices}) { + getMyNotification(); + } + + List<NotificationData> _notificationData = []; + List<NotificationDataAnnouncementUser> _notificationDataAnnouncementUser = []; + + resultState? _state; + countState? _cState; + + int _notificationCount = 0; + String _message = ''; + + List<NotificationData> get result => _notificationData; + List<NotificationDataAnnouncementUser> get resultAnnouncement => + _notificationDataAnnouncementUser; + + int get notificationCount => _notificationCount; + String get message => _message; + + resultState? get state => _state; + countState? get cState => _cState; + + changeIsRead(List<NotificationData> list, int index) { + list[index].isRead = "1"; + if (_notificationCount > 0) { + _notificationCount--; + } + notifyListeners(); + } + + changeAllRead() { + if (_state == resultState.HasData) { + for (final notif in _notificationData) { + notif.isRead = "1"; + } + _notificationCount = 0; + notifyListeners(); + } + } + + Future<dynamic> getMyNotification() async { + try { + _state = resultState.Loading; + notifyListeners(); + var notifications = await notificationServices.getNotification(); + List<NotificationData> myNotification = notifications[0]; + List<NotificationDataAnnouncementUser> myNotificationAnnouncement = + notifications[1]; + + if (myNotification.isEmpty && myNotificationAnnouncement.isEmpty) { + _state = resultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = resultState.HasData; + _notificationData = myNotification; + _notificationDataAnnouncementUser = myNotificationAnnouncement; + _notificationData + .sort((a, b) => b.timestamps!.compareTo(a.timestamps!)); + _notificationDataAnnouncementUser + .sort((a, b) => b.timestamps!.compareTo(a.timestamps!)); + _notificationCount = + _notificationData.where((notif) => notif.isRead == "0").length; + notifyListeners(); + return [_notificationData, _notificationDataAnnouncementUser]; + } + } catch (e, stacktrace) { + print(stacktrace.toString()); + _state = resultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + Future markAsRead() async { + try { + await notificationServices.readAllNotification(); + notifyListeners(); + } catch (e) { + if (kDebugMode) { + print("Error: --> $e"); + } + } + } + + Future getNotificationCount() async { + try { + _cState = countState.Loading; + notifyListeners(); + + int notificationCount = + _notificationData.where((notif) => notif.isRead == "0").length; + if (notificationCount == 0) { + _cState = countState.NoData; + _notificationCount = 0; + notifyListeners(); + } else { + _cState = countState.HasData; + _notificationCount = notificationCount; + notifyListeners(); + } + } catch (e) { + _cState = countState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + decreaseNotificationCount() { + if (_notificationCount > 0) { + _notificationCount--; + } + notifyListeners(); + } +} diff --git a/lib/providers/order_provider.dart b/lib/providers/order_provider.dart new file mode 100644 index 0000000..ab84bd6 --- /dev/null +++ b/lib/providers/order_provider.dart @@ -0,0 +1,118 @@ +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/order_model.dart'; + +enum ResultState { loading, succes, failed } + +class OrderProvider with ChangeNotifier { + ResultState? _state; + ResultState? get state => _state; + + String? _totalPrice; + String? _discountPrice; + String? _idCourse; + String? _thumbnail; + String? _title; + String? _instructor; + + String? get totalPrice => _totalPrice; + String? get discountPrice => _discountPrice; + String? get idCourse => _idCourse; + String? get thumbnail => _thumbnail; + String? get title => _title; + String? get instructor => _instructor; + + set selectedThumbnail(String value) { + _thumbnail = value; + } + + set selectedTitle(String value) { + _title = value; + } + + set selectedInstructor(String value) { + _instructor = value; + } + + void getTotalPrice(String total) { + _totalPrice = total; + } + + void getdiscountPrice(String discountPrice) { + _discountPrice = discountPrice; + } + + void getIdCourse(String idCourse) { + _idCourse = idCourse; + } + + List<OrderModel> _orders = []; + List<OrderModel> get orders => _orders; + + List<Map<String, String>> _invoice = []; + List<Map<String, String>> get invoice => _invoice; + + void addOrder({ + String? id, + String? title, + String? price, + String? imageUrl, + String? discountPrice, + String? instructor, + }) { + _orders.add( + OrderModel( + idCourse: id as String, + title: title as String, + price: price as String, + instructor: instructor as String, + imageUrl: imageUrl!, + discountPrice: discountPrice as String, + ), + ); + _invoice.add({ + "id_kursus": id, + "title_kursus": title, + "qty": "1", + "harga": discountPrice == "0" ? price : discountPrice + }); + } + + void removeOrder( + {String? id, + String? title, + String? price, + String? imageUrl, + String? discountPrice, + String? instructor}) { + _orders.remove(OrderModel( + idCourse: id ?? '', + title: title ?? '', + price: price ?? '', + instructor: instructor ?? '', + imageUrl: imageUrl ?? '', + discountPrice: discountPrice ?? '', + )); + _invoice.remove({ + "id_kursus": id ?? '', + "title_kursus": title ?? '', + "qty": "1", + "harga": (discountPrice == "0" || discountPrice == null) + ? price + : discountPrice + }); + } + + void clear() { + _orders = []; + _invoice = []; + } + + // void addInvoice({ + // required String id, + // required String title, + // required String price, + // }) { + // _invoice.add( + // {"id_kursus": id, "title_kursus": title, "qty": "1", "harga": price}); + // } +} diff --git a/lib/providers/others_course_provider.dart b/lib/providers/others_course_provider.dart new file mode 100644 index 0000000..c31989e --- /dev/null +++ b/lib/providers/others_course_provider.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/services/course_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class OthersCourseProvider with ChangeNotifier { + final CourseService otherCourseService; + OthersCourseProvider({required this.otherCourseService}) { + getOthersCourse(); + } + List<CourseModel> _othersCourse = []; + + ResultState? _state; + + String _message = ''; + + List<CourseModel> get result => _othersCourse; + + ResultState? get state => _state; + + String get message => _message; + int _page = 1; + int get page => _page; + bool _loading = true; + bool get loading => _loading; + set course(List<CourseModel> course) { + _othersCourse = course; + notifyListeners(); + } + + Future<dynamic> getOthersCourse() async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CourseModel> course = + await otherCourseService.getOthersCourse(_page); + + if (course.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + course.shuffle(); + _state = ResultState.HasData; + notifyListeners(); + return _othersCourse = course; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } + + Future<dynamic> getOthersCourses() async { + try { + List<CourseModel> res = + await otherCourseService.getOthersCourse(_page + 1); + if (res.isNotEmpty) { + res.shuffle(); + _page += 1; + notifyListeners(); + return _othersCourse.addAll(res); + } else { + _loading = false; + notifyListeners(); + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/page_provider.dart b/lib/providers/page_provider.dart new file mode 100644 index 0000000..db90ba9 --- /dev/null +++ b/lib/providers/page_provider.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class PageProvider with ChangeNotifier { + int _currentIndex = 0; + + int get currentIndex => _currentIndex; + + set currentIndex(int index) { + _currentIndex = index; + notifyListeners(); + } + + remove() { + _currentIndex = 0; + } +} diff --git a/lib/providers/payments_provider.dart b/lib/providers/payments_provider.dart new file mode 100644 index 0000000..462ca22 --- /dev/null +++ b/lib/providers/payments_provider.dart @@ -0,0 +1,167 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/detail_order_model.dart'; +import 'package:initial_folder/models/detail_order_model_underscore.dart'; +import 'package:initial_folder/models/zero_price_model.dart'; +import 'package:initial_folder/services/payment_service.dart'; + +import '../screens/checkout/snap_payment_page.dart'; + +enum ResultState { error, success, gagal, loading } + +enum Process { uninitialized, loading } + +class PaymentsProvider with ChangeNotifier { + PaymentsProvider({required this.paymentServices}); + final PaymentServices paymentServices; + + ResultState? _state; + Process _stateProcess = Process.uninitialized; + bool _paymentModel = false; + ZeroPrice? _zeroPrice; + String? _idOrders; + List<DetailOrderModel> _detailOrder = []; + List<DetailOrderModelUnderscore> _detailOrderUnderscore = []; + + ResultState? get state => _state; + Process get stateProcess => _stateProcess; + bool get result => _paymentModel; + String? get idOrders => _idOrders; + ZeroPrice? get zeroPrice => _zeroPrice; + List<DetailOrderModel> get detailOrder => _detailOrder; + List<DetailOrderModelUnderscore> get detailOrderUnderscore => _detailOrderUnderscore; + + String get orderId { + if (_detailOrder.isNotEmpty) { + return _detailOrder[0].idOrder; + } else { + return ''; + } + } + + set selectedIdOrders(String value) { + _idOrders = value; + notifyListeners(); + } + + // mulai Snap Payment + Future<Map<String, String>?> startSnapPayment({ + required String orderId, + required int grossAmount, + required List<Map<String, dynamic>> dataInvoice, +}) async { + try { + _stateProcess = Process.loading; + notifyListeners(); + + Map<String, String> transactionToken = await paymentServices.getSnapTransactionToken( + orderId: orderId, + grossAmount: grossAmount, + dataInvoice: dataInvoice, + ); + + if (transactionToken.isNotEmpty) { + _stateProcess = Process.uninitialized; + _state = ResultState.success; + print('Transaction token didapatkan: $transactionToken'); + notifyListeners(); + return transactionToken; + } else { + _state = ResultState.gagal; + _stateProcess = Process.uninitialized; + notifyListeners(); + return null; + } + } catch (e) { + print('Error Snap Payment -> $e'); + _state = ResultState.error; + _stateProcess = Process.uninitialized; + notifyListeners(); + return null; + } +} + + Future<Map<String, String>> getSnapTransactionToken({ + required String orderId, + required int grossAmount, + required List<Map<String, dynamic>> dataInvoice, + }) async { + return await paymentServices.getSnapTransactionToken( + orderId: orderId, + grossAmount: grossAmount, + dataInvoice: dataInvoice, + ); + } + + // kursus gratis + Future<bool> freeCourse(int _idCourse) async { + try { + _stateProcess = Process.loading; + notifyListeners(); + bool result = await paymentServices.freeCoure(_idCourse); + if (result) { + _stateProcess = Process.uninitialized; + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.gagal; + _stateProcess = Process.uninitialized; + notifyListeners(); + return false; + } + } catch (e) { + print('Error -> $e'); + _state = ResultState.error; + _stateProcess = Process.uninitialized; + notifyListeners(); + return false; + } + } + + // kursus dengan kupon gratis + Future<bool> freeCourseCoupon(int _idCourse, String coupon) async { + try { + _stateProcess = Process.loading; + notifyListeners(); + bool result = await paymentServices.freeCoureCoupon(_idCourse, coupon); + if (result) { + _stateProcess = Process.uninitialized; + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.gagal; + _stateProcess = Process.uninitialized; + notifyListeners(); + return false; + } + } catch (e) { + print('Error -> $e'); + _state = ResultState.error; + _stateProcess = Process.uninitialized; + notifyListeners(); + return false; + } + } + + //pembayaran dengan harga 0 (ZeroPrice) + Future<dynamic> zeroPayment(List<Map<String, String>> dataInvoice, String totalPayment) async { + try { + notifyListeners(); + var result = await paymentServices.zeroPayment( + dataInvoice: dataInvoice, totalPayment: totalPayment); + if (result.isNotEmpty) { + notifyListeners(); + return true; + } else { + notifyListeners(); + return false; + } + } catch (e) { + print('Error -> $e'); + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/play_video_course_provider.dart b/lib/providers/play_video_course_provider.dart new file mode 100644 index 0000000..b26210e --- /dev/null +++ b/lib/providers/play_video_course_provider.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; + +class PlayVideoCourseProvider with ChangeNotifier { + String _url = ''; + + String get url => _url; + + set uri(String videoUrl) { + _url = videoUrl; + notifyListeners(); + } + + indexUri(String indexUri) { + _url = indexUri; + notifyListeners(); + } +} diff --git a/lib/providers/posting_announcement_reply_provider.dart b/lib/providers/posting_announcement_reply_provider.dart new file mode 100644 index 0000000..7bd3717 --- /dev/null +++ b/lib/providers/posting_announcement_reply_provider.dart @@ -0,0 +1,34 @@ +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/services/announcement_service.dart'; + +enum ResultState { uninitilized, loading, failed, success } + +class PostingAnnouncementReplyProvider with ChangeNotifier { + ResultState _state = ResultState.uninitilized; + + ResultState get state => _state; + + Future<bool> postAnnouncementReply(String textBody, String idAnnouncmenet, + String tokenAnnouncement, String idAnnouncement) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await AnnouncementService() + .replyAnnouncement(tokenAnnouncement, textBody, idAnnouncement); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/posting_qna_provider.dart b/lib/providers/posting_qna_provider.dart new file mode 100644 index 0000000..02ae90d --- /dev/null +++ b/lib/providers/posting_qna_provider.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/services/qna_service.dart'; + +enum ResultState { uninitilized, loading, failed, success } + +class PostingQnaProvider with ChangeNotifier { + QnaDataModel? _qnaDataModel; + QnaDataModel? get wishlistPostModel => _qnaDataModel; + + setwishlistPostModel(QnaDataModel? qnaDataModel) { + _qnaDataModel = qnaDataModel; + notifyListeners(); + } + + ResultState _state = ResultState.uninitilized; + + ResultState get state => _state; + + //Posting QNA + Future<bool> postingQna( + String title, + String quest, + String idCourse, + String idLesson, + ) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = + await QnaService().postingQna(title, quest, idCourse, idLesson); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } + + //Update QNA + Future<bool> editQna(String idCourse, String quest, int idQna, String title, String idLesson) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await QnaService().updateQna(idCourse, quest, idQna, title, idLesson); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } + + //Delete QNA + Future<bool> deleteQna(int idQna) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await QnaService().deleteQna(idQna); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/posting_qna_reply_provider.dart b/lib/providers/posting_qna_reply_provider.dart new file mode 100644 index 0000000..127c053 --- /dev/null +++ b/lib/providers/posting_qna_reply_provider.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/services/qna_service.dart'; + +enum ResultState { uninitilized, loading, failed, success } + +class PostingQnaReplyProvider with ChangeNotifier { + ResultState _state = ResultState.uninitilized; + + ResultState get state => _state; + + //Post Qna Reply + Future<bool> postQnaReply(String textRep, String idQna) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await QnaService().postQnaReply(textRep, idQna); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } + + //Update QNA Reply + Future<bool> editQnaReply(String textRep, int idRep, String idQna) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await QnaService().editQnaReply(idRep, textRep, idQna); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } + + // Delete Qna Reply + Future<bool> deleteReplyQna(int idRep) async { + try { + _state = ResultState.loading; + notifyListeners(); + + bool response = await QnaService().deleteReplyQna(idRep); + if (response) { + _state = ResultState.success; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/posting_review_provider.dart b/lib/providers/posting_review_provider.dart new file mode 100644 index 0000000..634b2a2 --- /dev/null +++ b/lib/providers/posting_review_provider.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/services/course_service.dart'; + +enum ResultState { uninitilized, loading, failed, successAdd, successUpdate } + +class PostingReviewProvider with ChangeNotifier { + final CourseService courseService; + + PostingReviewProvider({required this.courseService}); + + ResultState _state = ResultState.uninitilized; + + ResultState get state => _state; + + Future<bool> postingReview( + String _review, int _courseId, int _valueRating) async { + try { + _state = ResultState.loading; + notifyListeners(); + List response = await courseService.postingReviewCourse( + _review, _courseId, _valueRating); + if (response[0] == true && response[1] == 201) { + _state = ResultState.successAdd; + notifyListeners(); + return true; + } else if (response[0] == true && response[1] == 200) { + _state = ResultState.successUpdate; + notifyListeners(); + return true; + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } +} diff --git a/lib/providers/profile_image_provider.dart b/lib/providers/profile_image_provider.dart new file mode 100644 index 0000000..a89e5d6 --- /dev/null +++ b/lib/providers/profile_image_provider.dart @@ -0,0 +1,34 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/profile_image_post_model.dart'; +import 'package:initial_folder/services/profile_image_service.dart'; + +class ProfileImageProvider with ChangeNotifier { + ProfileImagePostModel? _imageModel; + File? _imageFile; + + ProfileImagePostModel? get imageModel => _imageModel; + File? get imageFile => _imageFile; + + set imageModel(ProfileImagePostModel? _imageModel) { + _imageModel = imageModel; + notifyListeners(); + } + + void setImageFile(File? file) { + _imageFile = file; + notifyListeners(); + } + + Future<bool> addProfileImage({required File pckFile}) async { + try { + ProfileImagePostModel? imageModel = + await ProfileImageService().addProfileImage(pckFile: pckFile); + _imageModel = imageModel; + return true; + } catch (e) { + return false; + } + } +} diff --git a/lib/providers/profile_provider.dart b/lib/providers/profile_provider.dart new file mode 100644 index 0000000..fdcdb54 --- /dev/null +++ b/lib/providers/profile_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class ProfileProvider with ChangeNotifier { + int _currentIndex = 0; + + int get currentIndex => _currentIndex; + + set currentIndex(int index) { + _currentIndex = index; + notifyListeners(); + } +} diff --git a/lib/providers/promo_course_provider.dart b/lib/providers/promo_course_provider.dart new file mode 100644 index 0000000..1bb5ee1 --- /dev/null +++ b/lib/providers/promo_course_provider.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/services/course_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class PromoCourseProvider with ChangeNotifier { + final CourseService courseService; + PromoCourseProvider({required this.courseService}) { + getPromoCourse(); + } + + List<CourseModel> _course = []; + + ResultState? _state; + + String _message = ''; + + List<CourseModel> get result => _course; + + ResultState? get state => _state; + + String get message => _message; + + set course(List<CourseModel> course) { + _course = course; + notifyListeners(); + } + + Future<dynamic> getPromoCourse() async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CourseModel> course = await courseService.getPromoCourse(); + if (course.isEmpty) { + _state = ResultState.NoData; + print("Tidak ada data promo dri api"); + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _course = course; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/qna_provider.dart b/lib/providers/qna_provider.dart new file mode 100644 index 0000000..4d482d2 --- /dev/null +++ b/lib/providers/qna_provider.dart @@ -0,0 +1,50 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/services/qna_service.dart'; +import 'package:rxdart/rxdart.dart'; + +enum ResultState { loading, noData, hasData, error } + +enum ResultStateLike { loading, error } + +class QnaProvider with ChangeNotifier { + final _service = QnaService(); + StreamController<QnaModel> streamController = BehaviorSubject(); + String _message = ''; + String get message => _message; + ResultState? _state; + ResultState? get state => _state; + QnaModel? _qnaModel; + QnaModel? get result => _qnaModel; + set qnaModel(QnaModel? qnaModel) { + _qnaModel = qnaModel; + notifyListeners(); + } + + Stream<QnaModel> get qnaStream { + return streamController.stream; + } + + //Get QNA + Future<void> getQna(String idCourse) async { + try { + QnaModel qnaModel = await _service.getMyQna(idCourse); + if (qnaModel.error == true && qnaModel.status == 404) { + // Jika respons adalah 404 (tidak ditemukan), tangani objek dummy di sini + // Misalnya, menampilkan pesan kepada pengguna bahwa tidak ada data yang ditemukan + // Atau menampilkan UI yang sesuai + print('masuk rerror dsiinii'); + streamController.add(qnaModel); + } else { + // Jika respons adalah 200 (berhasil), tambahkan data ke dalam stream + streamController.add(qnaModel); + } + } catch (error) { + // Tangani jenis error lain jika diperlukan + rethrow; + } + } +} diff --git a/lib/providers/radeem_voucher_provider.dart b/lib/providers/radeem_voucher_provider.dart new file mode 100644 index 0000000..9ca9619 --- /dev/null +++ b/lib/providers/radeem_voucher_provider.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/voucher_model.dart'; +import 'package:initial_folder/services/voucher_service.dart'; + +enum ResultState { loading, failed, success, empty } + +class RadeemVoucherProvider with ChangeNotifier { + ResultState _state = ResultState.loading; + String? _message; + String? _messageCart; + String? _messageCancel; + + ResultState get state => _state; + String? get message => _message; + String? get messageCart => _messageCart; + String? get messageCancel => _messageCancel; + + VoucherModel? _detailKupon; + + VoucherModel? get result => _detailKupon; + + set detailKupon(VoucherModel? detail) { + _detailKupon = detail; + notifyListeners(); + } + + void clearState() { + _state = ResultState.empty; + notifyListeners(); + } + + Future<dynamic> radeemVoucher(int? idCourse, String voucher) async { + try { + _state = ResultState.loading; + notifyListeners(); + + VoucherModel? response = + await VoucherService().radeemVoucher(idCourse, voucher); + if (response.status == 200) { + _state = ResultState.success; + print("Berhasil kupon di provider ${_detailKupon}"); + notifyListeners(); + return _detailKupon = response; + // notifyListeners(); + } else { + _state = ResultState.failed; + print("Gagal kupon di provider 1 ${_detailKupon}"); + notifyListeners(); + return _detailKupon = response; + } + } catch (e) { + _state = ResultState.failed; + print("Gagal kupon di provider ${e}"); + _message = 'Error --> $e'; + notifyListeners(); + return null; + } + } + + Future redeemVoucherCart(List<String> idCourse, String voucher) async { + try { + _state = ResultState.loading; + notifyListeners(); + + VoucherModel response = + await VoucherService().redeemVoucherCart(idCourse, voucher); + if (response.status == 200) { + _state = ResultState.success; + + notifyListeners(); + return true; + // notifyListeners(); + } else { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } catch (e) { + _state = ResultState.failed; + notifyListeners(); + return false; + } + } + + Future<bool> deleteCoupon(_coupon) async { + try { + VoucherModel deleteCoupon = await VoucherService().cancelCoupon(_coupon); + if (deleteCoupon.status == 200) { + _state = ResultState.success; + notifyListeners(); + + notifyListeners(); + } else if (deleteCoupon.status == 404) { + _state = ResultState.failed; + notifyListeners(); + } + return true; + } catch (e) { + print("Exceptions: $e"); + return false; + } + } +} diff --git a/lib/providers/registrasi_google_provider.dart b/lib/providers/registrasi_google_provider.dart new file mode 100644 index 0000000..d0c608e --- /dev/null +++ b/lib/providers/registrasi_google_provider.dart @@ -0,0 +1,15 @@ +import 'package:flutter/foundation.dart'; + +class RegistrasiGoogleProvider extends ChangeNotifier { + String? _name; + String? _email; + + String? get name => _name; + String? get email => _email; + + void setNameAndEmail(String name, String email) { + _name = name; + _email = email; + notifyListeners(); + } +} diff --git a/lib/providers/reply_announcement_provider.dart b/lib/providers/reply_announcement_provider.dart new file mode 100644 index 0000000..612c7bc --- /dev/null +++ b/lib/providers/reply_announcement_provider.dart @@ -0,0 +1,40 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/services/announcement_service.dart'; +import 'package:rxdart/rxdart.dart'; + +enum DataState { loading, noData, hasData, error } + +enum ResultStateLike { loading, error } + +class ReplyAnnouncementProvider with ChangeNotifier { + final _service = AnnouncementService(); + + StreamController<AnnouncementModel> streamController = BehaviorSubject(); + + String _message = ''; + String get message => _message; + + DataState? _state; + DataState? get state => _state; + + AnnouncementModel? _announcementModel; + AnnouncementModel? get result => _announcementModel; + + set announcementModel(AnnouncementModel? announcementModel) { + _announcementModel = announcementModel; + notifyListeners(); + } + + Stream<AnnouncementModel> get replyAnnouncementStream { + return streamController.stream; + } + + Future<void> getReplyAnnouncement(String idCourse, int index) async { + AnnouncementModel announcementModel = + await _service.getAnnouncement(idCourse); + streamController.add(announcementModel); + } +} diff --git a/lib/providers/reply_qna_provider.dart b/lib/providers/reply_qna_provider.dart new file mode 100644 index 0000000..8131124 --- /dev/null +++ b/lib/providers/reply_qna_provider.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/services/qna_service.dart'; +import 'package:rxdart/rxdart.dart'; + +enum DataState { loading, noData, hasData, error } +enum ResultStateLike { loading, error } + +class ReplyQnaProvider with ChangeNotifier { + final _service = QnaService(); + StreamController<QnaModel> streamController = BehaviorSubject(); + String _message = ''; + String get message => _message; + DataState? _state; + DataState? get state => _state; + QnaModel? _qnaModel; + QnaModel? get result => _qnaModel; + + set qnaModel(QnaModel? qnaModel) { + _qnaModel = qnaModel; + notifyListeners(); + } + + Stream<QnaModel> get replyQnaStream { + return streamController.stream; + } + + Future<void> getReplyQna(String idCourse, int index) async { + QnaModel qnaModel = await _service.getMyQna(idCourse); + streamController.add(qnaModel); + } + + Future<void> getReplyQnaById(String idCourse, String idQna) async { + try { + _state = DataState.loading; + QnaModel qnaModel = await _service.getMyQna(idCourse); + + // Filter untuk mencari item dengan idQna yang sesuai dalam List<List<QnaDataModel>> + var filteredQna = qnaModel.data.expand((innerList) => innerList) + .where((qna) => qna.idQna == idQna).toList(); + + if (filteredQna.isNotEmpty) { + // Set data yang sudah terfilter ke dalam streamController + streamController.add(QnaModel(data: [filteredQna])); + _state = DataState.hasData; + } else { + // Jika tidak ada data, set status noData + _state = DataState.noData; + } + } catch (e) { + _state = DataState.error; + _message = "Terjadi kesalahan saat mengambil data balasan."; + streamController.addError(_message); + } + notifyListeners(); + } + + @override + void dispose() { + streamController.close(); + super.dispose(); + } +} diff --git a/lib/providers/reset_provider.dart b/lib/providers/reset_provider.dart new file mode 100644 index 0000000..bb0cbf6 --- /dev/null +++ b/lib/providers/reset_provider.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/reset_model.dart'; +import 'package:initial_folder/services/reset_service.dart'; + +class ResetProvider with ChangeNotifier { + ResetModel? _reset; + ResetModel? get reset => _reset; + + set reset(ResetModel? reset) { + _reset = reset; + notifyListeners(); + } + + Future<bool> resetPassword({ + required String email, + }) async { + try { + ResetModel? reset = await ResetService().kirimEmail( + email: email, + ); + + _reset = reset; + return true; + } catch (e) { + print("EXception: $e"); + return false; + } + } +} diff --git a/lib/providers/search_provider.dart b/lib/providers/search_provider.dart new file mode 100644 index 0000000..5a3d02f --- /dev/null +++ b/lib/providers/search_provider.dart @@ -0,0 +1,99 @@ +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/services/search_service.dart'; + +enum ResultState { loading, noData, hasData, error } + +class SearchProvider with ChangeNotifier { + final SearchService searchService; + SearchProvider({required this.searchService}); + + ResultState? _state; + String _message = ''; + List<CourseModel> _searchCourse = []; + String _search = ''; + String _searchFilter = ''; + + ResultState? get state => _state; + List<CourseModel> get result => _searchCourse; + String get message => _message; + String get search => _search; + String get searchFilter => _searchFilter; + String get searchText => _search; + String get searchTextFilter => _searchFilter; + + void resetState() { + _state = null; + } + + set searchText(String text) { + _search = text; + notifyListeners(); + } + + set searchTextFilter(String text) { + _searchFilter = text; + notifyListeners(); + } + + void clearSearchBox() { + _search = ''; + notifyListeners(); + } + + initSearchCourse({ + String price = '', + String level = '', + String language = '', + String rating = '', + }) async { + try { + _state = ResultState.loading; + + List<CourseModel> course = await searchService.filter( + price: price, + level: level, + language: language, + rating: rating, + keyword: _search, + ); + + if (course.isEmpty) { + _state = ResultState.noData; + notifyListeners(); + _message = 'Empty Data'; + } else { + _state = ResultState.hasData; + + _searchCourse = course; + } + } catch (e) { + _state = ResultState.error; + + _message = 'Error --> $e'; + } + notifyListeners(); + } + + Future<dynamic> searchCourse(judul) async { + try { + _state = ResultState.loading; + notifyListeners(); + List<CourseModel> course = await searchService.search(judul); + + if (course.isEmpty) { + _state = ResultState.noData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.hasData; + notifyListeners(); + return _searchCourse = course; + } + } catch (e) { + _state = ResultState.error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/section_lesson_course_provider.dart b/lib/providers/section_lesson_course_provider.dart new file mode 100644 index 0000000..23b9299 --- /dev/null +++ b/lib/providers/section_lesson_course_provider.dart @@ -0,0 +1,54 @@ +import 'package:flutter/foundation.dart'; +import 'package:initial_folder/models/section_lesson_model.dart'; +import 'package:initial_folder/services/section_lesson_service.dart'; + +enum ResultState { Loading, HasData, Error, NoData } + +class SectionLessonCourseProvider with ChangeNotifier { + final SectionLessonService sectionLessonService; + final String id; + SectionLessonCourseProvider( + {required this.sectionLessonService, required this.id}) { + getDetailCourse(id); + } + + SectionLessonModel? _sectionLessonModel; + + SectionLessonModel? get result => _sectionLessonModel; + + ResultState? _state; + + String _message = ''; + + ResultState? get state => _state; + + String get message => _message; + + set detailCourse(SectionLessonModel detail) { + _sectionLessonModel = detail; + notifyListeners(); + } + + Future<dynamic> getDetailCourse(_id) async { + try { + _state = ResultState.Loading; + notifyListeners(); + SectionLessonModel detail = + await sectionLessonService.getSectionLessonCourse(_id); + if (detail.data![0].isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Tidak ada data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _sectionLessonModel = detail; + } + } catch (e) { + print('ini erroprnyaa' + e.toString()); + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/section_lesson_provider.dart b/lib/providers/section_lesson_provider.dart new file mode 100644 index 0000000..03d3e25 --- /dev/null +++ b/lib/providers/section_lesson_provider.dart @@ -0,0 +1,41 @@ +import 'package:flutter/foundation.dart'; + +class SectionLessonProvider with ChangeNotifier { + bool isExpanded; + bool isCheckBoxLesson; + bool isOutcomes; + bool isDescription; + bool isDescriptionInstruktur; + SectionLessonProvider({ + this.isExpanded = true, + this.isCheckBoxLesson = false, + this.isOutcomes = false, + this.isDescription = true, + this.isDescriptionInstruktur = false, + }); + + void expanded() { + isExpanded = !isExpanded; + notifyListeners(); + } + + void checkBoxLesson() { + isCheckBoxLesson = !isCheckBoxLesson; + notifyListeners(); + } + + void detailPlayCourseOutcomes() { + isOutcomes = !isOutcomes; + notifyListeners(); + } + + void descriptionPlayCourse() { + isDescription = !isDescription; + notifyListeners(); + } + + void descriptionInstrukturPlayCourse() { + isDescriptionInstruktur = !isDescriptionInstruktur; + notifyListeners(); + } +} diff --git a/lib/providers/selected_title_provider.dart b/lib/providers/selected_title_provider.dart new file mode 100644 index 0000000..27b47b9 --- /dev/null +++ b/lib/providers/selected_title_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class SelectedTitleProvider extends ChangeNotifier { + String? _selectedTitle; + + String? get selectedTitle => _selectedTitle; + + set selectedTitle(String? value) { + _selectedTitle = value; + notifyListeners(); + } +} diff --git a/lib/providers/show_hide_pw/show_hide_prov1.dart b/lib/providers/show_hide_pw/show_hide_prov1.dart new file mode 100644 index 0000000..66676bb --- /dev/null +++ b/lib/providers/show_hide_pw/show_hide_prov1.dart @@ -0,0 +1,11 @@ +import 'package:flutter/cupertino.dart'; + +class ShowHidePassword1 with ChangeNotifier { + bool password; + ShowHidePassword1({this.password = false}); + + void showPassword() { + password = !password; + notifyListeners(); + } +} diff --git a/lib/providers/show_hide_pw/show_hide_prov2.dart b/lib/providers/show_hide_pw/show_hide_prov2.dart new file mode 100644 index 0000000..7f16a5b --- /dev/null +++ b/lib/providers/show_hide_pw/show_hide_prov2.dart @@ -0,0 +1,11 @@ +import 'package:flutter/cupertino.dart'; + +class ShowHidePassword2 with ChangeNotifier { + bool password; + ShowHidePassword2({this.password = false}); + + void showPassword() { + password = !password; + notifyListeners(); + } +} diff --git a/lib/providers/show_hide_pw/show_hide_provider.dart b/lib/providers/show_hide_pw/show_hide_provider.dart new file mode 100644 index 0000000..f7ce4a0 --- /dev/null +++ b/lib/providers/show_hide_pw/show_hide_provider.dart @@ -0,0 +1,11 @@ +import 'package:flutter/cupertino.dart'; + +class ShowHidePassword with ChangeNotifier { + bool password; + ShowHidePassword({this.password = false}); + + void showPassword() { + password = !password; + notifyListeners(); + } +} diff --git a/lib/providers/stream_invoice_provider.dart b/lib/providers/stream_invoice_provider.dart new file mode 100644 index 0000000..9d7b269 --- /dev/null +++ b/lib/providers/stream_invoice_provider.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/detail_invoice_model.dart'; +import 'package:initial_folder/services/detail_invoice_service.dart'; +import 'package:provider/provider.dart'; + +enum ResultState { loading, noData, hasData, error } + +class StreamInvoiceProvider extends ChangeNotifier { + final DetailInvoiceService _detailInvoiceService = DetailInvoiceService(); + List<DataDetailInvoiceModel>? _detailInvoice; + String? _message; + String? _thumbnail; + ResultState? _state; + final StreamController<String> _transactionStatusController = + StreamController<String>.broadcast(); + + Stream<String> get transactionStatusStream => + _transactionStatusController.stream; + String? get message => _message; + String? get thumbnail => _thumbnail; + List<DataDetailInvoiceModel>? get detailInvoice => _detailInvoice; + ResultState? get state => _state; + + set selectedThumbnail(String? value) { + _thumbnail = value; + notifyListeners(); + } + + Future<void> fetchDetailInvoice(String? orderId) async { + print("Fetching detail invoice for order ID: $orderId"); + try { + _state = ResultState.loading; + notifyListeners(); + final data = await _detailInvoiceService.detailInvoice(orderId); + if (data.isEmpty) { + _state = ResultState.noData; + _message = 'Invoice Detail is Empty'; + print("Invoice detail is empty"); + } else { + _state = ResultState.hasData; + _detailInvoice = data; + print( + "Invoice detail fetched, updating transaction status to: ${data[0].transactionStatus}"); + _transactionStatusController.sink.add(data[0].transactionStatus!); + } + } catch (e) { + _state = ResultState.error; + _message = 'Error -> $e'; + print("Error fetching invoice detail: $e"); + } + notifyListeners(); + } + + @override + void dispose() { + _transactionStatusController.close(); + super.dispose(); + } +} diff --git a/lib/providers/tab_play_course_provider.dart b/lib/providers/tab_play_course_provider.dart new file mode 100644 index 0000000..e7d5157 --- /dev/null +++ b/lib/providers/tab_play_course_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class TabPlayCourseProvider with ChangeNotifier { + int _currentIndex = 0; + + int get currentIndex => _currentIndex; + + set currentIndex(int index) { + _currentIndex = index; + notifyListeners(); + } +} diff --git a/lib/providers/tab_provider.dart b/lib/providers/tab_provider.dart new file mode 100644 index 0000000..6fba2ea --- /dev/null +++ b/lib/providers/tab_provider.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; + +class TabProvider with ChangeNotifier { + int _currentIndex = 0; + + int get currentIndex => _currentIndex; + + set currentIndex(int index) { + _currentIndex = index; + notifyListeners(); + } +} diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart new file mode 100644 index 0000000..9d8897f --- /dev/null +++ b/lib/providers/theme_provider.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class ThemeProvider with ChangeNotifier { + ThemeData _themeData = ThemeClass.darkmode; + + ThemeProvider() { + loadTheme(); + } + + ThemeData get themeData => _themeData; + + set themeData(ThemeData themeData) { + _themeData = themeData; + notifyListeners(); + } + + void toggleTheme() { + if (_themeData == ThemeClass.lightmode) { + themeData = ThemeClass.darkmode; + _saveTheme('dark'); + } else { + themeData = ThemeClass.lightmode; + _saveTheme('light'); + } + } + + Future<void> loadTheme() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? theme = prefs.getString('theme'); + if (theme == null) { + var brightness = + WidgetsBinding.instance.platformDispatcher.platformBrightness; + + if (brightness == Brightness.light) { + themeData = ThemeClass.lightmode; + _saveTheme('light'); + } else { + themeData = ThemeClass.darkmode; + _saveTheme('dark'); + } + } else if (theme == 'dark') { + themeData = ThemeClass.darkmode; + } else { + themeData = ThemeClass.lightmode; + } + notifyListeners(); + } + + Future<void> saveCurrentTheme() async { + String theme = _themeData == ThemeClass.darkmode ? 'dark' : 'light'; + await _saveTheme(theme); + } + + Future<void> _saveTheme(String theme) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setString('theme', theme); + } +} diff --git a/lib/providers/top_course_provider.dart b/lib/providers/top_course_provider.dart new file mode 100644 index 0000000..9a6b20f --- /dev/null +++ b/lib/providers/top_course_provider.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/services/course_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class TopCourseProvider with ChangeNotifier { + final CourseService courseService; + TopCourseProvider({required this.courseService}) { + getTopCourse(); + } + + List<CourseModel> _course = []; + + ResultState? _state; + + String _message = ''; + + List<CourseModel> get result => _course; + + ResultState? get state => _state; + + String get message => _message; + + set course(List<CourseModel> course) { + _course = course; + notifyListeners(); + } + + Future<dynamic> getTopCourse() async { + try { + _state = ResultState.Loading; + notifyListeners(); + List<CourseModel> course = await courseService.getTopCourse(); + if (course.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _course = course; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/total_price_provider.dart b/lib/providers/total_price_provider.dart new file mode 100644 index 0000000..a40eb9b --- /dev/null +++ b/lib/providers/total_price_provider.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; + +class TotalPriceProvider with ChangeNotifier { + int? _totalPrice; + int? _totalPrices; + int? _priceCoupon; + int? _finalPriceCoupon; + int? _potonganKupon; + int? _penguranganHarga; + String? _typeCoupon; + String? _couponText; + String? _valuePrice; + String? _subTotal; + + int? get totalPrice => _totalPrice; + int? get totalPrices => _totalPrices; + int? get priceCoupon => _priceCoupon; + int? get finalPriceCoupon => _finalPriceCoupon; + int? get potonganKupon => _potonganKupon; + int? get penguranganHarga => _penguranganHarga; + String? get typeCoupon => _typeCoupon; + String? get couponText => _couponText; + String? get valuePrice => _valuePrice; + String? get subTotal => _subTotal; + + set selectedTotalPrice(int value) { + _totalPrice = value; + notifyListeners(); + } + + set selectedTotalPrices(int value) { + _totalPrices = value; + notifyListeners(); + } + + set selectedPriceCoupon(int value) { + _priceCoupon = value; + notifyListeners(); + } + + set selectedTypeCoupon(String value) { + _typeCoupon = value; + notifyListeners(); + } + + set selectedFinalPriceCoupon(int value) { + _finalPriceCoupon = value; + notifyListeners(); + } + + set selectedCouponText(String value) { + _couponText = value; + notifyListeners(); + } + + set selectedPotonganKupon(int value) { + _potonganKupon = value; + notifyListeners(); + } + + set selectedValuePrice(String value) { + _valuePrice = value; + notifyListeners(); + } + + set selectedPenguranganHarga(int value) { + _penguranganHarga = value; + notifyListeners(); + } + + set selectedSubTotal(String value) { + _subTotal = value; + notifyListeners(); + } +} diff --git a/lib/providers/update_data_diri_provider.dart b/lib/providers/update_data_diri_provider.dart new file mode 100644 index 0000000..5fb0aab --- /dev/null +++ b/lib/providers/update_data_diri_provider.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/update_data_diri_model.dart'; +import 'package:initial_folder/services/user_info_service.dart'; + +enum ResultState { loading, succes } + +class UpdateDataDiriProvider with ChangeNotifier { + UpdateDataDiriModel? _updateDataDiriModel; + + UpdateDataDiriModel? get updateDataDiriModel => _updateDataDiriModel; + ResultState? _state; + ResultState? get state => _state; + set updateDataDiriModel(UpdateDataDiriModel? _updateDataDiriModel) { + _updateDataDiriModel = updateDataDiriModel; + notifyListeners(); + } + + Future<bool> dataDiriUpdate( + {String? fullname, + String? biograph, + String? twitter, + String? facebook, + String? linkedin, + String? instagram, + String? datebirth, + String? phone, + String? gender, + String? headline, + String? email}) async { + try { + _state = ResultState.loading; + notifyListeners(); + _updateDataDiriModel = await UserInfoService().updateDataDiri( + fullname: fullname, + biograph: biograph, + phone: phone, + // datebirth: datebirth, + // gender: gender, + facebook: facebook, + linkedin: linkedin, + twitter: twitter, + instagram: instagram, + headline: headline, + email: email, + ); + if (_updateDataDiriModel != null) { + if (_updateDataDiriModel!.status == 200) { + _state = ResultState.succes; + notifyListeners(); + return true; + } + } + + return false; + } catch (e) { + print("Exception: $e"); + _updateDataDiriModel = null; + return false; + } + } +} diff --git a/lib/providers/update_password_provider.dart b/lib/providers/update_password_provider.dart new file mode 100644 index 0000000..4dfd260 --- /dev/null +++ b/lib/providers/update_password_provider.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/update_password_model.dart'; +import 'package:initial_folder/services/user_info_service.dart'; + +class UpdatePasswordProvider with ChangeNotifier { + UpdatePasswordModel? _updatePasswordModel; + + UpdatePasswordModel? get updatePasswordModel => _updatePasswordModel; + + set updatePasswordModel(UpdatePasswordModel? _updatePasswordModel) { + _updatePasswordModel = updatePasswordModel; + notifyListeners(); + } + + Future<bool> passwordUpdate({ + required idUser, + required String? email, + required String? oldPassword, + required String? password, + required String? newPasswordConfirm, + }) async { + try { + UpdatePasswordModel? updatePasswordModel = await UserInfoService() + .updatePassword( + idUser: idUser, + email: email, + oldPassword: oldPassword, + password: password, + newPasswordConfirm: newPasswordConfirm); + + _updatePasswordModel = updatePasswordModel; + //print(user); + return true; + } catch (e) { + print("excecptiasd gagal: $e"); + return false; + } + } +} diff --git a/lib/providers/user_info_provider.dart b/lib/providers/user_info_provider.dart new file mode 100644 index 0000000..eefc893 --- /dev/null +++ b/lib/providers/user_info_provider.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/user_info_model.dart'; +import 'package:initial_folder/services/user_info_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class UserInfoProvider with ChangeNotifier { + final UserInfoService userInfoService; + + UserInfoProvider({ + required this.userInfoService, + }); + + UserInfoModel? _userInfoModel; + + ResultState? _state; + + String _message = ''; + + UserInfoModel? get result => _userInfoModel; + + ResultState? get state => _state; + + String get message => _message; + + String get fullName => _userInfoModel?.data[0].fullname ?? ''; + + set userInfo(UserInfoModel userInfo) { + _userInfoModel = userInfo; + notifyListeners(); + } + + Future<dynamic> getUserInfo(_email) async { + try { + _state = ResultState.Loading; + notifyListeners(); + UserInfoModel userInfo = await userInfoService.getUserInfo(_email); + //print(userInfo.data[0].id_user); + if (userInfo.data.isEmpty) { + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + _state = ResultState.HasData; + notifyListeners(); + return _userInfoModel = userInfo; + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } + } +} diff --git a/lib/providers/whislist_provider.dart b/lib/providers/whislist_provider.dart new file mode 100644 index 0000000..cf5a3d1 --- /dev/null +++ b/lib/providers/whislist_provider.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/wishlist_model.dart'; +import 'package:initial_folder/services/wishlist_service.dart'; + +enum ResultState { Loading, NoData, HasData, Error } + +class WishlistProvider with ChangeNotifier { + WishlistProvider() { + getWishlist(); + } + WishlistModel? _wishlistModel; + ResultState? _state; + + String _message = ''; + List _data = []; + List get data => _data; + set dataWishlist(List value) { + _data = value; + notifyListeners(); + } + + WishlistModel? get result => _wishlistModel; + + ResultState? get state => _state; + + String get message => _message; + + set wishlistModel(WishlistModel wishlist) { + _wishlistModel = wishlist; + notifyListeners(); + } + + Future<dynamic> getWishlist() async { + try { + _state = ResultState.Loading; + notifyListeners(); + + WishlistModel wishlist = await WishlistService().getWishlist(); + if (wishlist.data.isEmpty) { + _data = []; + _state = ResultState.NoData; + notifyListeners(); + return _message = 'Empty Data'; + } else { + List<DataWihslistModel> wishlistItems = wishlist.data[0]; + + bool allCourseIdNull = wishlistItems.every((item) => item.courseId == null); + + if (allCourseIdNull) { + _data = []; + _state = ResultState.NoData; + notifyListeners(); + return _message = 'course_id nya null'; + } else { + _data = wishlistItems.map((e) => e.courseId).toList(); + _state = ResultState.HasData; + print('wishlist punya data'); + notifyListeners(); + return _wishlistModel = wishlist; + } + } + } catch (e) { + _state = ResultState.Error; + notifyListeners(); + return _message = 'Error --> $e'; + } +} +} diff --git a/lib/providers/wishlist_post_provider.dart b/lib/providers/wishlist_post_provider.dart new file mode 100644 index 0000000..78bd35d --- /dev/null +++ b/lib/providers/wishlist_post_provider.dart @@ -0,0 +1,43 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:initial_folder/models/wishlist_model.dart'; +import 'package:initial_folder/services/wishlist_service.dart'; + +class WishlistPostProvider with ChangeNotifier { + WishlistPostModel? _wishlistPostModel; + WishlistPostModel? get wishlistPostModel => _wishlistPostModel; + + setwishlistPostModel(WishlistPostModel? wishlistPostModel) { + _wishlistPostModel = wishlistPostModel; + notifyListeners(); + } + + Future<bool> addWishlist(int wishlistItem) async { + try { + WishlistPostModel? wishlistPostModel = + await WishlistService().addWishlist(wishlistItem); + _wishlistPostModel = wishlistPostModel; + return true; + } on SocketException { + return false; + } catch (e) { + print("Exception: $e"); + return false; + } + } + + Future<bool> deleteWishlist(int wishlistItem) async { + try { + WishlistPostModel? wishlistPostModel = + await WishlistService().deleteWishlist(wishlistItem); + _wishlistPostModel = wishlistPostModel; + return true; + } on SocketException { + return false; + } catch (e) { + print("Exception: $e"); + return false; + } + } +} diff --git a/lib/routes.dart b/lib/routes.dart new file mode 100644 index 0000000..c4ca68e --- /dev/null +++ b/lib/routes.dart @@ -0,0 +1,51 @@ +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/screens/checkout/success_paid_course.dart'; +import 'package:initial_folder/screens/course/sertif.dart'; +import 'package:initial_folder/screens/home/components/body_comp/lihat_semua_kursus_page.dart'; +import 'package:initial_folder/screens/home/components/home_page.dart'; +import 'package:initial_folder/screens/home/components/notification.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/reset/reset_screen.dart'; +import 'package:initial_folder/screens/login/reset/success_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi_pending.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_email/success_regis_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/incomplete_profile_screen.dart'; +import 'package:initial_folder/widgets/search_and_filter_course.dart'; + +import 'screens/profile/account_sign_in/riwayat_transaksi.dart'; + +// We use name route +// All our routes will be available here +final Map<String, WidgetBuilder> routes = { + HomeScreen.routeName: (context) => HomeScreen(), + HomePage.routeName: (context) => HomePage(), + // Splash.routeName: (context) => Splash(), + // RegistrationScreen.routeName: (context) => RegistrationScreen(), + RegistrationScreen.routeName: (context) => RegistrationEmail(), + RegistrationEmail.routeName: (context) => RegistrationEmail(), + LoginScreen.routeName: (context) => LoginScreen(), + LoginEmail.routeName: (context) => LoginEmail(), + ResetScreen.routeName: (context) => ResetScreen(), + RegisSuccess.routeName: (context) => RegisSuccess(), + ResetSuccess.routeName: (context) { + final email = ModalRoute.of(context)!.settings.arguments as String; + return ResetSuccess(email: email);}, + SearchAndFilterCourse.routeName: (context) => SearchAndFilterCourse(), + Sertif.routeName: (context) => Sertif(), + IncompleteProfile.routeName: (context) => IncompleteProfile(), + Notifikasi.routeName: (context) => Notifikasi(), + LihatSemuaKursus.routeName: (context) => LihatSemuaKursus(), + RiwayatTransaksi.routeName: (context) => RiwayatTransaksi(), + RiwayatTransaksiPending.routeName:(context) => RiwayatTransaksiPending(), + SuccessPaidCourse.routeName:(context) => SuccessPaidCourse(), + + // '/detail_course': (context) => DetailCourseScreen( + // otherCourseModel: + // ModalRoute.of(context)?.settings.arguments as OthersCourseModel,id: ,), + // '/course_by_category': (context) => CourseByCategory( + // courseByCategory: ModalRoute.of(context)?.settings.arguments as NewCourseModel, id: '',), +}; diff --git a/lib/screens/.gitkeep b/lib/screens/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/screens/cart/cart_page.dart b/lib/screens/cart/cart_page.dart new file mode 100644 index 0000000..c0905f2 --- /dev/null +++ b/lib/screens/cart/cart_page.dart @@ -0,0 +1,874 @@ +import 'dart:async'; +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/models/carts_model.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/cart/components/cart_list.dart'; +import 'package:initial_folder/screens/checkout/checkout_cart_page.dart'; +import 'package:initial_folder/screens/checkout/components/field_kupon.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/providers/order_provider.dart' as orderProv; +import 'package:initial_folder/providers/radeem_voucher_provider.dart' + as radeemVoucher; + +class CartPage extends StatefulWidget { + const CartPage({ + Key? key, + this.idcourse, + this.cartsModel, + this.isiVoucher, + }) : super(key: key); + + final String? idcourse; + final DataCartsModel? cartsModel; + final String? isiVoucher; + + @override + State<CartPage> createState() => _CartPageState(); +} + +class _CartPageState extends State<CartPage> { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of<CartsProvider>(context, listen: false).getCarts(); + }); + } + bool isChecked = true; + Set<int> selectedItems = {}; + bool isMultiSelectionMode = false; + bool isToastShowing = false; + + void _onItemLongPressed(int index) { + setState(() { + isMultiSelectionMode = true; + selectedItems.add(index); + }); + } + + void _onItemTapped(int index) { + // setState(() { + // if (selectedItems.contains(index)) { + // selectedItems.remove(index); + // if (selectedItems.isEmpty) { + // isMultiSelectionMode = false; + // } + // } else { + // selectedItems.add(index); + // } + // }); + } + + void _selectAllItems(bool selectAll, int itemCount) { + setState(() { + if (selectAll) { + for (int i = 0; i < itemCount; i++) { + selectedItems.add(i); + } + } else { + selectedItems.clear(); + } + isChecked = selectAll; + }); + } + + void _showToast(String message) { + isToastShowing = true; + CherryToast.error( + title: Text(message), + animationDuration: Durations.medium1, + animationType: AnimationType.fromTop, + autoDismiss: true, + ).show(context); + Timer(Duration(seconds: 2), () { + isToastShowing = false; + }); + } + + @override + Widget build(BuildContext context) { + List<String> idCarts = []; + Provider.of<orderProv.OrderProvider>(context, listen: false).clear(); + TextEditingController kuponController = TextEditingController(); + var kuponIsApplied = TextEditingController(); + final selectedTotalPrice = Provider.of<TotalPriceProvider>(context); + + Widget _validasiKupon() { + return Dialog( + elevation: 5.0, + child: Padding( + padding: const EdgeInsets.all(29), + child: Text( + 'Kupon tidak valid atau sudah habis', + textAlign: TextAlign.center, + ), + ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)), + ); + } + + Widget kupon(List<String> idKursus) { + Provider.of<orderProv.OrderProvider>(context).clear(); + return Consumer<CartsProvider>( + builder: (context, state, _) { + var resultData = state.result!.data.length; + + for (var i = 0; i <= resultData - 1; i++) { + if (state.result?.data[i].coupon != null) { + kuponIsApplied = TextEditingController( + text: state.result?.data[i].coupon?.codeCoupon ?? ''); + return Column( + children: [ + Row( + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(4), + top: getProportionateScreenHeight(14)), + child: FieldKupon( + prefix: IconButton( + onPressed: () async { + await Provider.of< + radeemVoucher + .RadeemVoucherProvider>(context, + listen: false) + .deleteCoupon(kuponIsApplied.text); + kuponIsApplied.text = ''; + kuponIsApplied.clear(); + Provider.of<orderProv.OrderProvider>(context, + listen: false) + .clear(); + await Provider.of<CartsProvider>(context, + listen: false) + .getCarts(); + }, + icon: Icon(Icons.close), + iconSize: 15, + color: secondaryColor, + ), + controler: kuponIsApplied, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(13)), + Container( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(12), + vertical: getProportionateScreenHeight(8), + ), + ), + onPressed: () async { + final voucher = kuponIsApplied.text; + if (await Provider.of< + radeemVoucher.RadeemVoucherProvider>( + context, + listen: false) + .redeemVoucherCart( + idKursus, + voucher, + )) { + kuponIsApplied = TextEditingController( + text: state + .result?.data.last.coupon?.codeCoupon); + + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => CartPage(), + ), + ); + } else { + showDialog( + barrierColor: Color.fromARGB(70, 24, 24, 24), + context: context, + builder: (context) { + return _validasiKupon(); + }, + ); + } + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Gunakan', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + color: baruTextutih, + fontSize: getProportionateScreenWidth(13), + fontWeight: reguler, + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Image.asset( + "assets/icons/cart_gunakan.png", + color: baruTextutih, + width: getProportionateScreenWidth(16), + ), + ], + ), + ), + ), + ], + ), + ], + ); + } + } + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Container( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(4), + top: getProportionateScreenHeight(14)), + child: FieldKupon( + controler: kuponController, + ), + ), + ), + SizedBox(width: 14), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(12), + vertical: getProportionateScreenHeight(8), + ), + ), + onPressed: () async { + final voucher = kuponController.text; + + if (await Provider.of< + radeemVoucher.RadeemVoucherProvider>(context, + listen: false) + .redeemVoucherCart(idKursus, voucher)) { + Provider.of<CartsProvider>(context, listen: false) + .getCarts(); + kuponController.clear(); + } else { + showDialog( + barrierColor: Color.fromARGB(70, 24, 24, 24), + context: context, + builder: (context) { + return _validasiKupon(); + }, + ); + } + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Gunakan', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + color: baruTextutih, + fontSize: getProportionateScreenWidth(13), + fontWeight: reguler, + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Image.asset( + "assets/icons/cart_gunakan.png", + width: getProportionateScreenWidth(16), + color: baruTextutih, + ), + ], + ), + ), + ], + ) + ], + ); + }, + ); + } + + Widget custombottomCart( + String price, String total, List<String> coursesId) { + return SingleChildScrollView( + child: Container( + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor.withOpacity(0.9) + : baruTextutih.withOpacity(0.3), + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + top: getProportionateScreenHeight(12), + ), + child: Row( + children: [ + Text( + 'Subtotal Harga Kursus', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + Spacer(), + SizedBox(width: getProportionateScreenWidth(8)), + if (int.parse(price) < 50000) + RichText( + text: TextSpan( + children: [ + TextSpan( + text: numberFormat(total), + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + decoration: TextDecoration.lineThrough, + color: fourthColor, + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 2.8, + ), + ), + WidgetSpan( + child: SizedBox( + width: getProportionateScreenWidth(8))), + TextSpan( + text: numberFormat(int.parse(price) < 50000 + ? (int.parse(price) - 5000).toString() + : price), + style: thirdTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 2.8, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ) + else + RichText( + text: TextSpan( + children: [ + TextSpan( + text: "", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + decoration: TextDecoration.lineThrough, + color: fourthColor, + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 2.8, + ), + ), + WidgetSpan( + child: SizedBox( + width: getProportionateScreenWidth(8))), + TextSpan( + text: numberFormat(price), + style: thirdTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 2.8, + color: Theme.of(context).colorScheme.onPrimary, + ), + ) + ], + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(3)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Biaya Layanan', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + Text( + numberFormat(int.parse(price) < 50000 ? "5000" : "0"), + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(3)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Potongan Kupon', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + Text( + numberFormat("0"), + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(16)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Bayar', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 3.2, + ), + ), + Text( + numberFormat(price), + style: thirdTextStyle.copyWith( + fontWeight: bold, + fontSize: SizeConfig.blockHorizontal! * 3.2, + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(12)), + Consumer<CartsProvider>( + builder: (context, state, _) { + int _hasil = 0; + var priceDiscount = state.result!.data + .map((e) => int.parse(e.discountPrice ?? '0')) + .toList(); + + var diskonHarga = 0; + for (var i = 0; i < state.data.length; i++) { + diskonHarga += priceDiscount[i]; + } + var potonganKupon = state.result!.potonganKupon; + + return Container( + color: Theme.of(context).colorScheme.primaryContainer, + height: getProportionateScreenHeight(52), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Row( + children: [ + // GestureDetector( + // onTap: () { + // _selectAllItems( + // !isChecked, state.result!.data.length); + // }, + // child: Container( + // decoration: BoxDecoration( + // color: isChecked + // ? (Theme.of(context).brightness == + // Brightness.light + // ? baruTexthitam + // : baruTextutih) + // : Theme.of(context).brightness == + // Brightness.light + // ? baruTextutih + // : seventeenColor, + // shape: BoxShape.rectangle, + // border: Border.all( + // color: isChecked + // ? Theme.of(context).brightness == + // Brightness.light + // ? baruTexthitam + // : baruTextutih + // : (Theme.of(context).brightness == + // Brightness.light + // ? baruTexthitam.withOpacity(0.7) + // : Colors.grey), + // width: getProportionateScreenWidth(3), + // ), + // borderRadius: BorderRadius.circular(6), + // ), + // width: getProportionateScreenWidth(18), + // height: getProportionateScreenHeight(16), + // child: isChecked + // ? Icon( + // Icons.check, + // size: getProportionateScreenWidth(13), + // color: Theme.of(context) + // .colorScheme + // .background, + // ) + // : null, + // ), + // ), + SizedBox(width: getProportionateScreenWidth(5)), + // Text( + // 'Semua', + // style: thirdTextStyle.copyWith( + // fontWeight: reguler, + // fontSize: SizeConfig.blockHorizontal! * 3, + // ), + // ), + SizedBox(width: getProportionateScreenWidth(13)), + Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Total', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 3.4, + ), + ), + Text( + numberFormat(price), + style: thirdTextStyle.copyWith( + fontWeight: bold, + fontSize: SizeConfig.blockHorizontal! * 3.8, + ), + ), + ], + ), + Spacer(), + Container( + child: DefaultButton( + isCart: true, + text: 'Checkout (${selectedItems.length})', + press: () { + if (!isToastShowing) { + if (selectedItems.isEmpty) { + _showToast("Anda belum memilih kursus!"); + } else { + selectedTotalPrice.selectedTotalPrice = + int.parse(price); + setState(() { + if (potonganKupon != null) { + _hasil = potonganKupon; + } + }); + Navigator.of(context).push( + CustomNavigator( + child: CheckoutCartPage( + idCart: idCarts, + potonganKupon: _hasil, + discountHarga: diskonHarga, + isCart: true, + ), + ), + ); + } + } + }, + ), + ), + ], + ), + ), + ); + }, + ), + ], + ), + ), + ); + } + + return Scaffold( + resizeToAvoidBottomInset: true, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Keranjang', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + body: Consumer<CartsProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + List<String> coursesId = []; + + var carts = state.result!.data; + for (int i = 0; i < carts.length; i++) { + idCarts.add(carts[i].cartId!); + } + for (int i = 0; i < carts.length; i++) { + coursesId.add(carts[i].courseId!); + } + WidgetsBinding.instance.addPostFrameCallback((_) { + if (state.result != null && selectedItems.isEmpty) { + _selectAllItems(true, state.result!.data.length); + } + }); + return Stack( + children: [ + SingleChildScrollView( + child: Container( + margin: + EdgeInsets.only(left: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${state.result?.data.length} Kursus di Keranjang ', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(13)), + ListView.builder( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: state.result!.data.length, + itemBuilder: (context, index) { + var carts = state.result!.data[index]; + + Provider.of<orderProv.OrderProvider>(context) + .addOrder( + id: carts.courseId, + title: carts.title, + price: carts.price, + imageUrl: carts.thumbnail ?? + 'https://vocasia.id/uploads/thumbnails/course_thumbnails/course_thumbnail_default_63.jpg', + discountPrice: carts.finalPrice, + instructor: carts.instructor!, + ); + bool isSelected = selectedItems.contains(index); + return GestureDetector( + onTap: () => _onItemTapped(index), + child: CartList( + idCourse: carts.courseId ?? '0', + id: carts.cartId ?? '', + image: carts.thumbnail ?? + 'https://vocasia.id/uploads/thumbnails/course_thumbnails/course_thumbnail_default_63.jpg', + title: carts.title ?? '', + instruktur: carts.instructor ?? '', + price: carts.price ?? '', + discountPrice: carts.finalPrice ?? '', + isSelected: isSelected, + ), + ); + }, + ), + SizedBox(height: 90), + ], + ), + ), + ), + ], + ); + } else if (state.state == ResultState.NoData) { + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(height: 48), + Container( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Center( + child: Column( + children: [ + Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(100), + child: ColorFiltered( + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onPrimary, + BlendMode.srcATop, + ), + child: Image.asset('assets/images/search.png'), + ), + ), + SizedBox(height: 16), + Text( + "Keranjang Kosong", + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + SizedBox(height: 4), + Text( + "Keranjang kamu kosong, tetap berbelanja dan cari kursus", + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + SizedBox(height: 16), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(78), + vertical: getProportionateScreenHeight(10)), + child: DefaultButton( + weight: reguler, + text: 'Belanja Kursus', + press: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } else if (state.state == ResultState.Error) { + return AlertDialog( + title: const Text('Koneksi Internet'), + content: const Text('Terjadi Kesalahan'), + actions: <Widget>[ + TextButton( + onPressed: () => Navigator.pop(context, 'Cancel'), + child: const Text('Cancel'), + ), + ], + ); + } else { + return Center(child: Text('')); + } + }, + ), + bottomNavigationBar: Consumer<CartsProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(121), + child: Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ), + ); + } else if (state.state == ResultState.HasData) { + var total = state.result!.data.map((e) { + if (e.price != null && e.discountPrice != null) { + return int.parse(e.discountPrice!) == 0 + ? 0 + : int.parse(e.price!); + } else { + return 0; + } + }).toList(); + var result = 0; + for (var i = 0; i < total.length; i++) { + result += total[i]; + } + Provider.of<orderProv.OrderProvider>(context) + .getTotalPrice((state.result?.totalPayment.toString() ?? '0')); + var carts = state.result!.data; + List<String> coursesId = []; + + for (int i = 0; i < carts.length; i++) { + coursesId.add(carts[i].courseId!); + } + + selectedTotalPrice.selectedSubTotal = result.toString(); + + return custombottomCart( + state.result?.totalPayment.toString() ?? '0', + result.toString(), + coursesId, + ); + } else if (state.state == ResultState.NoData) { + return Text( + "", + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + color: tenthColor, + ), + ); + } else if (state.state == ResultState.Error) { + return Text(''); + } else { + return Center(child: Text('')); + } + }, + ), + ); + } +} + +// Align( + // alignment: Alignment.bottomCenter, + // child: SingleChildScrollView( + // child: Container( + // padding: EdgeInsets.symmetric( + // horizontal: getProportionateScreenWidth(16)), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // "Promotions", + // style: thirdTextStyle.copyWith( + // fontWeight: semiBold, + // letterSpacing: 1, + // fontSize: SizeConfig.blockHorizontal! * 3.4, + // ), + // ), + // kupon(coursesId), + // ], + // ), + // ), + // ), + // ), \ No newline at end of file diff --git a/lib/screens/cart/components/cart_list.dart b/lib/screens/cart/components/cart_list.dart new file mode 100644 index 0000000..553a180 --- /dev/null +++ b/lib/screens/cart/components/cart_list.dart @@ -0,0 +1,325 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart'; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart' as cartsProv; + +class CartList extends StatefulWidget { + const CartList({ + Key? key, + required this.image, + required this.title, + required this.instruktur, + required this.price, + required this.id, + required this.idCourse, + this.discountPrice, + this.isSelected = false, + }) : super(key: key); + + final String image, instruktur, title, price, id, idCourse; + final String? discountPrice; + final bool isSelected; + + @override + State<CartList> createState() => _CartListState(); +} + +class _CartListState extends State<CartList> { + @override + Widget build(BuildContext context) { + final pageProvider = Provider.of<PageProvider>(context); + + Future<void> getCartsLength() async {} + + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: EdgeInsets.only(bottom: getProportionateScreenHeight(20)), + child: Container( + width: getProportionateScreenWidth(330 + 108), + child: Stack( + children: [ + Container( + height: getProportionateScreenHeight(98), + width: getProportionateScreenWidth(330), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : sixteenColor, + ), + ), + Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(9), + right: getProportionateScreenWidth(15), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 11, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(156), + height: getProportionateScreenWidth(88), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(widget.image), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + ], + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.title, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: getProportionateScreenHeight(2)), + Text( + 'oleh ${widget.instruktur}', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: light, + ), + ), + Visibility( + visible: widget.discountPrice != "0", + child: Text( + numberFormat(widget.discountPrice), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ), + ), + Visibility( + visible: widget.discountPrice == widget.price, + child: SizedBox( + height: getProportionateScreenHeight(2)), + ), + Visibility( + visible: widget.discountPrice != widget.price, + child: Text( + numberFormat(widget.price), + style: thirdTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + color: secondaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ), + ), + ], + ), + ), + ], + ), + ), + // widget.isSelected + // ? Padding( + // padding: EdgeInsets.only( + // top: getProportionateScreenHeight(13), + // left: getProportionateScreenWidth(6), + // ), + // child: + // SvgPicture.asset("assets/icons/cart_checklist.svg"), + // ) + // : Padding( + // padding: EdgeInsets.only( + // top: getProportionateScreenHeight(13), + // left: getProportionateScreenWidth(6), + // ), + // child: + // SvgPicture.asset("assets/icons/cart_unchecklist.svg"), + // ), + widget.isSelected + ? Container( + height: getProportionateScreenHeight(98), + width: getProportionateScreenWidth(330), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: Theme.of(context).brightness == Brightness.light + ? baruTexthitam.withOpacity(0.1) + : baruTexthitam.withOpacity(0.3), + ), + ) + : Container(), + Consumer<cartsProv.CartsProvider>( + builder: (context, state, _) { + if (state.state == cartsProv.ResultState.Loading) { + return CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ); + } else { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () async { + await Provider.of<WishlistPostProvider>(context, + listen: false) + .addWishlist(int.parse(widget.idCourse)); + await Provider.of<WishlistProvider>(context, + listen: false) + .getWishlist(); + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: + Theme.of(context).colorScheme.background, + elevation: 0.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: + EdgeInsets.fromLTRB(12, 26, 22, 15), + content: Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(14)), + child: Text( + textAlign: TextAlign.left, + 'Berhasil memindahkan kursus ke wishlist', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + actions: [ + SizedBox( + width: getProportionateScreenWidth(10)), + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Kembali', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + ), + ), + ), + SizedBox( + width: getProportionateScreenWidth(3)), + GestureDetector( + onTap: () { + pageProvider.currentIndex = 3; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => HomeScreen(), + ), + (route) => false); + }, + child: Text( + 'Lihat Wishlist', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + ), + ), + ), + ], + ), + ); + }, + child: SvgPicture.asset( + "assets/icons/cart_wishlist.svg", + height: getProportionateScreenHeight(96), + width: getProportionateScreenWidth(45), + ), + ), + GestureDetector( + onTap: () async { + await Provider.of<CartProvider>(context, + listen: false) + .deleteCart(widget.id); + Provider.of<OrderProvider>(context, listen: false) + .removeOrder( + id: widget.idCourse, + title: widget.title, + price: widget.price, + imageUrl: widget.image, + discountPrice: widget.discountPrice, + instructor: widget.instruktur, + ); + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: + Theme.of(context).colorScheme.background, + elevation: 0.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: + EdgeInsets.fromLTRB(12, 26, 22, 15), + content: Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(14)), + child: Text( + textAlign: TextAlign.center, + 'Berhasil menghapus kursus', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ); + await Provider.of<cartsProv.CartsProvider>(context, + listen: false) + .getCarts(); + }, + child: SvgPicture.asset( + "assets/icons/cart_remove.svg", + height: getProportionateScreenHeight(96), + width: getProportionateScreenWidth(45), + ), + ), + ], + ); + } + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/certificate/all_certificate.dart b/lib/screens/certificate/all_certificate.dart new file mode 100644 index 0000000..8dadb8d --- /dev/null +++ b/lib/screens/certificate/all_certificate.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/certificate_provider.dart'; +import 'package:initial_folder/screens/certificate/component/all_certificate_header.dart'; + +import 'package:initial_folder/screens/certificate/component/all_certificate_item.dart'; +import 'package:provider/provider.dart'; + +import '../../size_config.dart'; +import '../../theme.dart'; + +class AllCertificate extends StatelessWidget { + const AllCertificate({Key? key, this.isLogin}) : super(key: key); + + final bool? isLogin; + + @override + Widget build(BuildContext context) { + Future.delayed(Duration(seconds: 0), () async { + await Provider.of<CertificateProvider>(context, listen: false) + .getAllCertif(); + }); + + return isLogin != null + ? Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + ), + body: Center( + child: Text("Silahkan login terlebih dahulu"), + ), + ) + : Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Sertifikat', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16), + ), + ), + ), + body: RefreshIndicator( + displacement: 40, + color: primaryColor, + onRefresh: () async { + await Provider.of<CertificateProvider>(context, listen: false) + .getAllCertif(); + }, + child: Padding( + padding: EdgeInsets.only( + top: getProportionateScreenWidth(5), + right: getProportionateScreenWidth(20), + left: getProportionateScreenWidth(20), + ), + child: ListView( + children: [ + CertificateHeader(), + CertificateItem(), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/certificate/certificate.dart b/lib/screens/certificate/certificate.dart new file mode 100644 index 0000000..e42eb54 --- /dev/null +++ b/lib/screens/certificate/certificate.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/certificate/all_certificate.dart'; +import 'package:initial_folder/screens/certificate/certificatecheck/certificate_check.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; + +import '../../size_config.dart'; +import '../../theme.dart'; + +class Certificate extends StatelessWidget { + const Certificate({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Cek Status Sertifikat', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16)), + )), + body: Padding( + padding: EdgeInsets.all(15), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + "assets/images/undraw_certificate.png", + scale: getProportionateScreenHeight(2), + ), + SizedBox(height: 10), + Text( + "Cek keaslian sertifikat atau cek sertifikat dari kursus yang anda miliki", + style: TextStyle( + fontSize: getProportionateScreenHeight(12), + fontWeight: reguler), + textAlign: TextAlign.center, + ), + SizedBox(height: getProportionateScreenHeight(38)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + backgroundColor: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + padding: EdgeInsets.symmetric( + vertical: 5, + horizontal: getProportionateScreenWidth(5))), + onPressed: () { + Navigator.push( + context, + CustomNavigator( + child: CertificateCheck(), + ), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Verifikasi Sertifikat", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: baruTextutih, + fontWeight: reguler, + ), + ), + ], + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(12)), + SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + elevation: 0.0, + foregroundColor: primaryColor, + backgroundColor: + Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + side: BorderSide( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + width: getProportionateScreenWidth(1.5), + ), + ), + padding: EdgeInsets.symmetric( + vertical: 5, + horizontal: getProportionateScreenWidth(5), + ), + ), + onPressed: (!Condition.loginEmail && + !Condition.loginFirebase) + ? () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AllCertificate( + isLogin: false, + ), + ), + ); + } + : () { + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: AllCertificate(), + ), + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Sertifikat Saya", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontWeight: bold, + ), + ), + ], + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/certificate/certificatecheck/certificate_check.dart b/lib/screens/certificate/certificatecheck/certificate_check.dart new file mode 100644 index 0000000..3c672f6 --- /dev/null +++ b/lib/screens/certificate/certificatecheck/certificate_check.dart @@ -0,0 +1,195 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/models/check_certificate.model.dart'; +import 'package:initial_folder/providers/certificate_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/certificate/certificatecheck/certificate_check_dialog.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; // Import package ini + +class CertificateCheck extends StatefulWidget { + CertificateCheck({Key? key}) : super(key: key); + + @override + State<CertificateCheck> createState() => _CertificateCheckState(); +} + +class _CertificateCheckState extends State<CertificateCheck> { + final certificateController = TextEditingController(); + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + bool isLoading = false; + bool failed = false; + bool faileds = false; + String? errorMessage; + String _iconPath = 'assets/icons/not_checklist.svg'; + + void handleCertificate() async { + final certificateCode = certificateController.text.trim(); + + if (certificateCode.isNotEmpty) { + final url = "https://vocasia.id?no-certificate=$certificateCode"; + + if (await canLaunch(url)) { + await launch(url); + } else { + setState(() { + errorMessage = 'Tidak dapat membuka link, coba lagi.'; + }); + } + } else { + setState(() { + errorMessage = 'Mohon masukkan kode sertifikat.'; + }); + } + } + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Verifikasi Sertifikat', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16)), + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(18), + vertical: getProportionateScreenHeight(10), + ), + child: Center( + child: Column( + children: [ + SizedBox(height: getProportionateScreenHeight(20)), + Image.asset('assets/images/certificate.png'), + SizedBox(height: getProportionateScreenHeight(50)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + "Nomor Sertifikat", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12)), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : secondaryColor.withOpacity(0.3), + ), + height: 40, + child: Form( + key: _formKey, + child: TextFormField( + autofocus: false, + controller: certificateController, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ), + cursorColor: secondaryColor, + decoration: InputDecoration( + border: InputBorder.none, + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: sevenColor), + borderRadius: BorderRadius.circular(10)), + contentPadding: EdgeInsets.only( + left: getProportionateScreenWidth(20), + bottom: getProportionateScreenHeight(8), + top: getProportionateScreenHeight(2), + ), + hintText: 'Masukkan nomor sertifikat', + hintStyle: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: secondaryColor, + letterSpacing: 0.5, + ), + suffixIcon: Transform.scale( + scale: 0.5, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + child: SvgPicture.asset(_iconPath), + ), + ), + ), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + if (errorMessage != null) + Center( + child: Text( + errorMessage!, + textAlign: TextAlign.center, + style: TextStyle(color: Colors.red), + ), + ), + TextButton( + child: Text( + "Di mana letak nomor sertifikat?", + style: thirdTextStyle.copyWith( + color: Theme.of(context).colorScheme.onPrimary), + ), + onPressed: () { + showInfoDialog(context); + }, + ), + isLoading + ? LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: Color(0xff050505), + ) + : SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + backgroundColor: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + padding: EdgeInsets.symmetric( + vertical: 5, + horizontal: getProportionateScreenWidth(5))), + onPressed: () { + handleCertificate(); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Periksa", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: baruTextutih, + fontWeight: reguler, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/certificate/certificatecheck/certificate_check_dialog.dart b/lib/screens/certificate/certificatecheck/certificate_check_dialog.dart new file mode 100644 index 0000000..25e307d --- /dev/null +++ b/lib/screens/certificate/certificatecheck/certificate_check_dialog.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; + +showInfoDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return Dialog( + elevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(14.0)), + child: Container( + width: getProportionateScreenHeight(1), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Align( + alignment: Alignment.topRight, + child: IconButton( + padding: EdgeInsets.all(0), + onPressed: () { + Navigator.pop(context); + }, + icon: Icon(Icons.clear_rounded), + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenHeight(10)), + child: Image.asset("assets/images/no_sertif_preview.png"), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenHeight(10)), + child: Text( + "Nomor sertifikat terletak dikiri atas sertifikat, dibawah logo vocasia.", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: getProportionateScreenHeight(11), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + child: DefaultButton( + text: "OKE", + press: () { + Navigator.pop(context); + }), + ), + SizedBox(height: getProportionateScreenHeight(30)), + ], + ), + ), + ); + }, + ); +} diff --git a/lib/screens/certificate/component/all_certificate_header.dart b/lib/screens/certificate/component/all_certificate_header.dart new file mode 100644 index 0000000..d04883b --- /dev/null +++ b/lib/screens/certificate/component/all_certificate_header.dart @@ -0,0 +1,77 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/providers/certificate_provider.dart'; +import 'package:initial_folder/screens/certificate/component/just_certificate_count.dart'; +import 'package:provider/provider.dart'; +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class CertificateHeader extends StatelessWidget { + const CertificateHeader({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer<CertificateProvider>( + builder: (context, value, _) { + return RefreshIndicator( + onRefresh: () async { + await Provider.of<CertificateProvider>(context, listen: false) + .getAllCertif(); + }, + child: const Column( + children: [ + JustCertificateCount(), + // Visibility( + // visible: value.state != ResultState.Loading, + // child: Container( + // // decoration: BoxDecoration( + // // borderRadius: BorderRadius.circular(10), + // // color: Theme.of(context).brightness == Brightness.dark + // // ? seventeenColor + // // : secondaryColor.withOpacity(0.3), + // // ), + // height: 40, + // // child: TextField( + // // autofocus: false, + // // onSubmitted: (value) async { + // // if (value.trim().isNotEmpty) { + // // Provider.of<CertificateProvider>(context, listen: false) + // // .searchCertif(value); + // // } + // // }, + // // style: primaryTextStyle.copyWith( + // // fontSize: getProportionateScreenWidth(14), + // // letterSpacing: 0.5, + // // ), + // // cursorColor: secondaryColor, + // // decoration: InputDecoration( + // // border: InputBorder.none, + // // errorBorder: OutlineInputBorder( + // // borderSide: BorderSide(color: sevenColor), + // // borderRadius: BorderRadius.circular(10)), + // // contentPadding: EdgeInsets.only( + // // top: getProportionateScreenHeight(3.5)), + // // prefixIcon: Icon( + // // FeatherIcons.search, + // // size: 20, + // // color: Colors.grey, + // // ), + // // hintText: 'Cari sertifikat kursus', + // // hintStyle: primaryTextStyle.copyWith( + // // fontSize: getProportionateScreenWidth(12), + // // color: secondaryColor, + // // letterSpacing: 0.5, + // // ), + // // ), + // // ), + // ), + // ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/screens/certificate/component/all_certificate_item.dart b/lib/screens/certificate/component/all_certificate_item.dart new file mode 100644 index 0000000..e4e613a --- /dev/null +++ b/lib/screens/certificate/component/all_certificate_item.dart @@ -0,0 +1,421 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/models/my_certificate.dart'; +import 'package:initial_folder/providers/certificate_provider.dart'; +import 'package:initial_folder/screens/course/sertif.dart'; +import 'package:initial_folder/screens/course/sertif_view.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:initial_folder/screens/certificate/component/just_certificate_count.dart'; + +class CertificateItem extends StatefulWidget { + const CertificateItem({Key? key}) : super(key: key); + + @override + State<CertificateItem> createState() => _CertificateItemState(); +} + +class _CertificateItemState extends State<CertificateItem> { + @override + Widget build(BuildContext context) { + return Consumer<CertificateProvider>( + builder: (context, value, child) { + return certificateCountComponent('assets/icons/gold-medal.png', + value.allCertificateCount.toString(), 'Jumlah Sertifikat', context); + }, + ); + } + + Widget certificateCountComponent( + String icon, + String count, + String content, + BuildContext context, + ) { + return ShaderMask( + shaderCallback: (Rect rect) { + return LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black, + Colors.transparent, + Colors.transparent, + Colors.black + ], + stops: [0.0, 0.02, 0.98, 1.0], + ).createShader(rect); + }, + blendMode: BlendMode.dstOut, + child: Consumer<CertificateProvider>( + builder: (context, state, _) { + if (state.state == ResultState.HasData) { + return Column( + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + Container( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(10)), + height: getProportionateScreenWidth(550), + child: ListView.builder( + itemCount: state.allCertificate?.length, + itemBuilder: (context, index) { + return allCertificates(state, index, instructorProfile, + buttonOpacity, buttonActivator); + }, + ), + ) + ], + ); + } else if (state.state == ResultState.Loading) { + return Shimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Column( + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + ), + SizedBox(width: getProportionateScreenHeight(130)), + Container( + width: getProportionateScreenWidth(50), + height: getProportionateScreenHeight(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + ), + ], + ), + ], + ), + Container( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(10)), + height: getProportionateScreenWidth(500), + child: ListView.builder( + itemCount: 5, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(20)), + child: Container( + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + ), + ); + }, + ), + ) + ], + ), + ); + } else if (state.state == ResultState.NoData) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + JustCertificateCount(), + ], + ), + Center( + child: Padding( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(180)), + child: Text('Sertifikat Tidak Tersedia'), + ), + ) + ], + ); + } else if (state.state == ResultState.Error) { + return Center( + child: Text('Terjadi Kesalahan'), + ); + } else { + return Center( + child: Text(''), + ); + } + }, + ), + ); + } + + instructorProfile(DataMyCertificateModel certificate) { + if (certificate.fotoProfile == null) { + return Image.asset( + "assets/images/Profile Image.png", + width: getProportionateScreenWidth(21), + height: getProportionateScreenHeight(21), + ); + } else + return CircleAvatar( + radius: getProportionateScreenWidth(10), + backgroundImage: NetworkImage(certificate.fotoProfile!), + ); + } + + buttonOpacity(int percentage) { + if (percentage == 100) { + return primaryColor; + } else { + return Color.fromRGBO(237, 169, 35, 0.5); + } + } + + buttonActivator(int percentage, BuildContext context, + DataMyCertificateModel certificate) { + if (percentage == 100) { + return () { + Navigator.push( + context, + CustomNavigator( + child: Sertif( + idCourse: int.parse(certificate.courseId!), + totalProgress: certificate.progress, + ), + ), + ); + }; + } else { + return null; + } + } + + buttonActivatorView(int percentage, BuildContext context, + DataMyCertificateModel certificate) { + if (percentage == 100) { + return () { + Navigator.push( + context, + CustomNavigator( + child: SertifView( + idCourse: int.parse(certificate.courseId!), + totalProgress: certificate.progress, + ), + ), + ); + }; + } else { + return null; + } + } + + allCertificates( + state, index, instructorProfile, buttonOpacity, buttonActivator) { + return Padding( + padding: EdgeInsets.symmetric(vertical: getProportionateScreenHeight(8)), + child: Container( + height: getProportionateScreenHeight(120), + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(10)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 4), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.asset( + 'assets/images/certificate.png', + width: getProportionateScreenWidth(45), + height: getProportionateScreenHeight(45), + ), + SizedBox( + width: getProportionateScreenWidth(250), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(13), + horizontal: getProportionateScreenWidth(3)), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + state.allCertificate![index]!.title!, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: TextStyle( + fontSize: getProportionateScreenWidth(13), + fontWeight: reguler), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + children: [ + instructorProfile(state.allCertificate![index]!), + SizedBox(width: getProportionateScreenWidth(5)), + Container( + width: getProportionateScreenHeight(100), + child: Text( + state.allCertificate![index]!.instructor!, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: getProportionateScreenHeight(11)), + ), + ) + ], + ), + SizedBox(height: getProportionateScreenHeight(3)), + Row( + children: [ + Stack( + children: [ + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(270)), + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey), + ), + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(270)) * + state.allCertificate![index]!.progress! / + 100, + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor), + ), + ], + ), + Text( + " ${state.allCertificate![index]!.progress!.toString()}% ", + style: TextStyle( + fontSize: getProportionateScreenWidth(10), + fontWeight: bold), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: Size(getProportionateScreenWidth(5), + getProportionateScreenHeight(5)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + backgroundColor: primaryColor, + disabledForegroundColor: Colors.grey, + disabledBackgroundColor: Colors.grey, + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(6)), + ), + onPressed: buttonActivatorView( + state.allCertificate![index]!.progress!, + context, + state.allCertificate![index]!), + child: Row( + children: [ + Padding( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(2)), + child: Text( + "Lihat\nSertifikat", + style: TextStyle( + fontSize: getProportionateScreenHeight(5), + color: baruTextutih), + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + SvgPicture.asset( + 'assets/icons/eye.svg', + fit: BoxFit.fitWidth, + width: getProportionateScreenWidth(12), + ) + ], + ), + ), + SizedBox(width: getProportionateScreenWidth(7)), + ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: Size(getProportionateScreenWidth(5), + getProportionateScreenHeight(5)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + backgroundColor: primaryColor, + disabledForegroundColor: Colors.grey, + disabledBackgroundColor: Colors.grey, + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(6)), + ), + onPressed: buttonActivator( + state.allCertificate![index]!.progress!, + context, + state.allCertificate![index]!), + child: Row( + children: [ + Padding( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(2)), + child: Text( + "Download\nSertifikat", + style: TextStyle( + fontSize: getProportionateScreenHeight(5), + color: baruTextutih), + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + SvgPicture.asset( + 'assets/icons/download.svg', + fit: BoxFit.fitWidth, + width: getProportionateScreenWidth(12), + ) + ], + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + ], + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + Widget certificateCountComponents( + String count, + BuildContext context, + ) { + return Center( + child: ListView( + children: [ + Container( + padding: const EdgeInsets.all(20.0), + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height / 1.5), + child: Center( + child: Text(count), + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/certificate/component/just_certificate_count.dart b/lib/screens/certificate/component/just_certificate_count.dart new file mode 100644 index 0000000..1804eb3 --- /dev/null +++ b/lib/screens/certificate/component/just_certificate_count.dart @@ -0,0 +1,380 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/my_certificate.dart'; +import 'package:initial_folder/providers/certificate_provider.dart'; +import 'package:initial_folder/screens/certificate/component/all_certificate_header.dart'; +import 'package:initial_folder/screens/course/sertif.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class JustCertificateCount extends StatefulWidget { + const JustCertificateCount({Key? key}) : super(key: key); + + @override + State<JustCertificateCount> createState() => _JustCertificateCountState(); +} + +class _JustCertificateCountState extends State<JustCertificateCount> { + @override + Widget build(BuildContext context) { + return Consumer<CertificateProvider>( + builder: (context, value, child) { + return certificateCountComponent( + 'assets/icons/gold-medal.png', + value.allCertificateCount.toString(), + 'Jumlah Sertifikat Selesai', + context); + }, + ); + } + + Widget certificateCountComponent( + String icon, + String count, + String content, + BuildContext context, + ) { + return ShaderMask( + shaderCallback: (Rect rect) { + return LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black, + Colors.transparent, + Colors.transparent, + Colors.black + ], + stops: [0.0, 0.02, 0.98, 1.0], + ).createShader(rect); + }, + blendMode: BlendMode.dstOut, + child: Consumer<CertificateProvider>( + builder: (context, state, _) { + if (state.state == ResultState.HasData) { + return Column( + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Image.asset( + icon, + width: getProportionateScreenWidth(18), + height: getProportionateScreenHeight(18), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Text( + content, + style: thirdTextStyle.copyWith( + letterSpacing: 0.42, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12)), + ), + SizedBox(width: getProportionateScreenWidth(4)), + Text( + count, + style: thirdTextStyle.copyWith( + letterSpacing: 0.42, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(17)), + ), + ], + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ); + } else if (state.state == ResultState.Loading) { + return Shimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: Column( + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + ), + SizedBox(width: getProportionateScreenHeight(130)), + Container( + width: getProportionateScreenWidth(50), + height: getProportionateScreenHeight(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + ), + ], + ), + ], + ), + Container( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(10)), + height: getProportionateScreenWidth(500), + child: ListView.builder( + itemCount: 5, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(20)), + child: Container( + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + ), + ); + }, + ), + ) + ], + ), + ); + } else { + return Column( + children: [], + ); + } + }, + ), + ); + } + + instructorProfile(DataMyCertificateModel certificate) { + if (certificate.fotoProfile == null) { + return Image.asset( + "assets/images/Profile Image.png", + scale: getProportionateScreenWidth(12), + ); + } else + return CircleAvatar( + radius: getProportionateScreenWidth(8), + backgroundImage: NetworkImage(certificate.fotoProfile!), + ); + } + + buttonOpacity(int percentage) { + if (percentage == 100) { + return primaryColor; + } else { + return Color.fromRGBO(237, 169, 35, 0.5); + } + } + + buttonActivator(int percentage, BuildContext context, + DataMyCertificateModel certificate) { + if (percentage == 100) { + return () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => Sertif( + idCourse: int.parse(certificate.courseId!), + totalProgress: certificate.progress, + ), + ), + ); + }; + } else { + return null; + } + } + + allCertificates( + state, index, instructorProfile, buttonOpacity, buttonActivator) { + return Padding( + padding: EdgeInsets.symmetric(vertical: getProportionateScreenHeight(8)), + child: Container( + height: getProportionateScreenHeight(90), + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(8)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 4), + ), + ], + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Padding( + padding: EdgeInsets.all(getProportionateScreenHeight(10)), + child: Image.asset( + 'assets/icons/gold-medal.png', + fit: BoxFit.cover, + scale: getProportionateScreenWidth(2.2), + )), + Padding( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(5)), + child: Container( + height: double.infinity, + child: + VerticalDivider(color: Color.fromRGBO(52, 121, 148, 0.9)), + ), + ), + SizedBox( + width: getProportionateScreenWidth(150), + child: Padding( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(3), + horizontal: getProportionateScreenWidth(3)), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + state.allCertificate![index]!.title!, + overflow: TextOverflow.ellipsis, + maxLines: 2, + style: TextStyle( + fontSize: getProportionateScreenHeight(12), + fontWeight: reguler), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + instructorProfile(state.allCertificate![index]!), + SizedBox(width: getProportionateScreenWidth(5)), + Container( + width: getProportionateScreenHeight(100), + child: Text( + state.allCertificate![index]!.instructor!, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: getProportionateScreenHeight(10)), + ), + ) + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Expanded( + child: Stack( + children: [ + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(245)), + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + ), + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(245)) * + state.allCertificate![index]!.progress! / + 100, + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(3)), + child: Container( + child: Align( + alignment: Alignment.center, + child: Text( + "${state.allCertificate![index]!.progress!.toString()}%", + style: TextStyle( + fontSize: 10, + color: Colors.white, + fontWeight: bold), + ), + ), + ), + ) + ], + ), + ], + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + backgroundColor: Color.fromRGBO(45, 45, 45, 1), + disabledForegroundColor: + Color.fromRGBO(150, 150, 150, 1).withOpacity(0.38), + disabledBackgroundColor: + Color.fromRGBO(150, 150, 150, 1).withOpacity(0.12), + padding: EdgeInsets.symmetric( + vertical: 5, horizontal: getProportionateScreenWidth(5))), + onPressed: buttonActivator( + state.allCertificate![index]!.progress!, + context, + state.allCertificate![index]!), + child: Row( + children: [ + Text( + "Download\nSertifikat", + style: TextStyle( + fontSize: getProportionateScreenHeight(8), + color: buttonOpacity( + state.allCertificate![index]!.progress!)), + ), + SizedBox(width: getProportionateScreenWidth(5)), + Image.asset( + 'assets/icons/left-arrow.png', + color: + buttonOpacity(state.allCertificate![index]!.progress!), + fit: BoxFit.fitWidth, + width: getProportionateScreenWidth(20), + ) + ], + ), + ) + ], + ), + ), + ); + } + + Widget certificateCountComponents( + String count, + BuildContext context, + ) { + return Center( + child: ListView( + children: [ + Container( + padding: const EdgeInsets.all(20.0), + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height / 1.5), + child: Center( + child: Text(count), + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/checkout/batas_bayar.dart b/lib/screens/checkout/batas_bayar.dart new file mode 100644 index 0000000..27d57ce --- /dev/null +++ b/lib/screens/checkout/batas_bayar.dart @@ -0,0 +1,782 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_order_model.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/screens/checkout/components/bar_batas_bayar.dart'; +import 'package:initial_folder/screens/checkout/components/bottom_sheet_detail.dart'; +import 'package:initial_folder/screens/checkout/components/tab_bar_batas_bayar.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi_pending.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator_pop.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:pusher_client/pusher_client.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:initial_folder/screens/checkout/success_paid_course.dart'; +import 'package:initial_folder/providers/payments_provider.dart' as payProv; + +class BatasBayar extends StatefulWidget { + BatasBayar({ + this.historyTransactionModel, + this.isFromHistory, + this.idCart, + }); + + final HistoryTransactionModel? historyTransactionModel; + final bool? isFromHistory; + final List<String>? idCart; + + @override + State<BatasBayar> createState() => _BatasBayarState(); +} + +class _BatasBayarState extends State<BatasBayar> { + Channel? _channel; + String? statusTransaction; + String? statusMessage; + PusherClient? pusher; + String capitalize(String s) => + s[0].toUpperCase() + s.substring(1).toLowerCase(); + + Future<void> deleteCourse() async { + List<String> idCarts = widget.idCart!; + + for (var element in idCarts) { + await Provider.of<CartProvider>(context, listen: false) + .deleteCart(element); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + } + } + + Future<void> initPusher() async { + int? idUser = await UsersInfo().getIdUser(); + PusherClient pusher = PusherClient( + '92060797e94ac7033edb', PusherOptions(cluster: 'ap1'), + autoConnect: false); + + pusher.connect(); + + pusher.onConnectionStateChange((state) { + print(state!.currentState); + }); + + pusher.onConnectionError((error) { + print(error); + }); + + _channel = pusher.subscribe('payment-channel'); + + _channel!.bind( + 'paid-event-$idUser', + (event) async { + print("Event? ini apaan dah ${event!.data!}"); + if (mounted) { + final status = jsonDecode(event.data!); + final newStatusTransaction = status['status_code']; + final newStatusMessage = status['message']; + + if (newStatusMessage.contains("Berhasil !") && + newStatusTransaction == '200') { + await deleteCourse(); + if (mounted) { + setState(() { + statusTransaction = newStatusTransaction; + statusMessage = newStatusMessage; + }); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => SuccessPaidCourse(), + ), + (route) => false, + ); + } + } else if (newStatusMessage.contains("Dibatalkan") && + newStatusTransaction == '200') { + await deleteCourse(); + + if (mounted) { + setState(() { + statusTransaction = newStatusTransaction; + statusMessage = newStatusMessage; + }); + Navigator.pop(context); + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.background, + elevation: 0.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: EdgeInsets.fromLTRB(12, 26, 22, 15), + content: Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(14)), + child: Text( + textAlign: TextAlign.center, + "Transaksi berhasil dibatalkan", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ); + } + } + } + }, + ); + } + + @override + void initState() { + initPusher(); + super.initState(); + } + + Widget _buildBatasBayarHistory() { + List<DetailOrderModel> detailOrder = + Provider.of<payProv.PaymentsProvider>(context).detailOrder; + var orders = Provider.of<OrderProvider>(context, listen: false).orders; + var selectedOrder = + Provider.of<payProv.PaymentsProvider>(context, listen: false); + var selected = Provider.of<OrderProvider>(context); + selected.selectedThumbnail = orders[0].imageUrl; + selected.selectedTitle = orders[0].title; + selected.selectedInstructor = orders[0].instructor; + PageProvider pageProviders = Provider.of<PageProvider>(context); + pageProviders.remove(); + + Widget mandiriPay() { + return detailOrder[0].bankName == 'mandiri' + ? Column( + children: [ + Row( + children: [ + Text( + detailOrder[0].billerCode.toString(), + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: detailOrder[0].billerCode.toString())) + .then( + (_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Berhasil Menyalin Kode Pembayaran'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + ], + ) + : Row(); + } + + Widget indomartPay() { + return detailOrder[0].bankName == 'indomaret' + ? Column( + children: [ + Row( + children: [ + Text( + detailOrder[0].merchantId!, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData( + ClipboardData(text: detailOrder[0].merchantId!)) + .then( + (_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Berhasil Menyalin Kode Merchant'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + ), + ], + ), + ], + ) + : Row(); + } + + return Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + 'Selesaikan Pembayaran', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + size: getProportionateScreenWidth(17), + ), + onPressed: () { + selectedOrder.selectedIdOrders = ''; + Navigator.pop(context); + }, + ), + ), + body: SingleChildScrollView( + child: Consumer<payProv.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == payProv.Process.loading) { + return Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(20), + bottom: getProportionateScreenHeight(20), + right: getProportionateScreenWidth(20), + ), + child: Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(7), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Container( + width: getProportionateScreenWidth(110), + height: getProportionateScreenHeight(9), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Container( + width: getProportionateScreenWidth(220), + height: getProportionateScreenHeight(7), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(40)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(250), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(30)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(170), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + ), + ], + ), + ), + ); + } else if (state.state == payProv.ResultState.gagal && + state.stateProcess == payProv.Process.uninitialized) { + return Center( + child: Text('Gagal Mengambil Pesanan'), + ); + } else if (state.state == payProv.ResultState.success && + state.stateProcess == payProv.Process.uninitialized) { + return Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + SizedBox(height: getProportionateScreenHeight(10)), + Center( + child: Column( + children: [ + Text( + 'Batas akhir pembayaran sampai', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + DateFormat('E, d MMM y (H:m)') + .format(detailOrder[0].transactionTimeLimit), + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + 'Mohon melakukan pembayaran sebelum batas', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Text( + 'tanggal yang ditetapkan atau pesanan', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Text( + 'akan otomatis dibatalkan', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(25)), + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 4), + ), + ], + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Container( + padding: EdgeInsets.symmetric( + vertical: 16, + horizontal: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Metode Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + detailOrder[0].bankName == 'mandiri' + ? 'Mandiri Virtual Account' + : detailOrder[0].bankName == 'permata' + ? 'Permata Virtual Account' + : detailOrder[0].bankName == 'bni' + ? 'BNI Virtual Account' + : detailOrder[0].bankName == 'bca' + ? 'BCA Virtual Account' + : detailOrder[0].bankName == + 'indomaret' + ? 'Indomaret' + : 'Alfamart', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + Container( + width: getProportionateScreenWidth(50), + height: getProportionateScreenWidth(17), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + fit: BoxFit.fill, + image: detailOrder[0].bankName == + 'mandiri' + ? AssetImage( + "assets/images/mandiri.png") + : detailOrder[0].bankName == 'permata' + ? AssetImage( + "assets/images/permata.png") + : detailOrder[0].bankName == 'bni' + ? AssetImage( + "assets/images/bni.png") + : detailOrder[0].bankName == + 'bca' + ? AssetImage( + "assets/images/bca.png") + : detailOrder[0] + .bankName == + 'alfamart' + ? AssetImage( + "assets/images/alfamart.png") + : AssetImage( + "assets/images/indomaret.png"), + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + detailOrder[0].bankName == 'alfamart' || + detailOrder[0].bankName == 'indomaret' + ? 'Nomor Pesanan' + : 'Nomor Virtual Akun', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + detailOrder[0].virtualNumber ?? 'ABCD', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: + detailOrder[0].virtualNumber!)) + .then( + (_) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text(detailOrder[0] + .bankName == + 'alfamart' || + detailOrder[0].bankName == + 'indomaret' + ? 'Berhasil Menyalin Nomor Pesanan' + : 'Berhasil Menyalin Nomor Virtual Akun'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: + getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + detailOrder[0].bankName == 'indomaret' + ? Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(16), + bottom: getProportionateScreenHeight(8), + ), + child: Text( + 'Kode Merchant', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + ) + : SizedBox.shrink(), + detailOrder[0].bankName == 'mandiri' + ? Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(16), + bottom: getProportionateScreenHeight(8), + ), + child: Text( + 'Kode Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + ) + : SizedBox.shrink(), + indomartPay(), + mandiriPay(), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Total Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(detailOrder[0].totalPayment))}', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (context) { + return BottomSheetDetail(); + }, + ); + }, + child: Text( + "Detail Pembayaran", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: + getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Status Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Text( + capitalize( + detailOrder[0].transactionStatus!.toString()), + style: thirdTextStyle.copyWith( + color: primaryColor, + letterSpacing: 1, + fontWeight: medium, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(24)), + DefaultButton( + text: 'Belanja kursus lainnya', + weight: semiBold, + press: () { + pageProviders.currentIndex == 0; + Navigator.pushAndRemoveUntil( + context, + CustomNavigatorPop(child: HomeScreen()), + (route) => false, + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(12)), + SizedBox( + width: getProportionateScreenWidth(300), + height: getProportionateScreenHeight(38), + child: TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => RiwayatTransaksiPending(), + ), + ); + }, + child: Text( + "Cek Status Transaksi", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: semiBold, + color: primaryColor, + letterSpacing: 0.5, + ), + ), + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + side: BorderSide(color: primaryColor), + borderRadius: BorderRadius.circular( + getProportionateScreenWidth(10)), + ), + backgroundColor: Colors.transparent, + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(32)), + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 4), + ), + ], + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: 16, + horizontal: getProportionateScreenWidth(16)), + child: Text( + 'Cara Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + Divider( + color: Color(0xff2D2D2D), + thickness: 0.5, + height: 0.5, + ), + Container( + padding: EdgeInsets.symmetric( + vertical: 16, + horizontal: getProportionateScreenWidth(16)), + child: detailOrder[0].bankName == 'indomaret' || + detailOrder[0].bankName == 'alfamart' + ? BarBatasBayar(detailOrder[0].bankName!) + : TabBarBatasBayar()), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(30)), + ], + ), + ); + } else { + return Center( + child: Text('Terjadi Kesalahan'), + ); + } + }, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return _buildBatasBayarHistory(); + } +} diff --git a/lib/screens/checkout/batas_bayar_bank.dart b/lib/screens/checkout/batas_bayar_bank.dart new file mode 100644 index 0000000..b7fc860 --- /dev/null +++ b/lib/screens/checkout/batas_bayar_bank.dart @@ -0,0 +1,991 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_countdown_timer/current_remaining_time.dart'; +import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_order_model.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/screens/checkout/components/bar_batas_bayar.dart'; +import 'package:initial_folder/screens/checkout/components/bottom_sheet_detail.dart'; +import 'package:initial_folder/screens/checkout/components/tab_bar_batas_bayar.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/services/cancel_payment_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator_pop.dart'; +import 'package:initial_folder/widgets/login_regist/default_button_payment.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:pusher_client/pusher_client.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:initial_folder/screens/checkout/success_paid_course.dart'; +import 'package:initial_folder/providers/payments_provider.dart' as payProv; + +class BatasBayarBank extends StatefulWidget { + BatasBayarBank({ + this.historyTransactionModel, + this.isFromHistory, + this.idCart, + }); + + final HistoryTransactionModel? historyTransactionModel; + final bool? isFromHistory; + final List<String>? idCart; + + @override + State<BatasBayarBank> createState() => _BatasBayarBankState(); +} + +class _BatasBayarBankState extends State<BatasBayarBank> { + Channel? _channel; + String? statusTransaction; + String? statusMessage; + bool isLoading = false; + PusherClient? pusher; + Duration? remainingTime; + String capitalize(String s) => + s[0].toUpperCase() + s.substring(1).toLowerCase(); + + Future<void> saveRemainingTime(Duration remainingTime) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setInt('remainingTime', remainingTime.inSeconds); + } + + Future<void> deleteCourse() async { + List<String> idCarts = widget.idCart!; + + for (var element in idCarts) { + await Provider.of<CartProvider>(context, listen: false) + .deleteCart(element); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + } + } + + Future<void> initPusher() async { + int? idUser = await UsersInfo().getIdUser(); + PusherClient pusher = PusherClient( + '92060797e94ac7033edb', PusherOptions(cluster: 'ap1'), + autoConnect: false); + + pusher.connect(); + + pusher.onConnectionStateChange((state) { + print(state!.currentState); + }); + + pusher.onConnectionError((error) { + print(error); + }); + + _channel = pusher.subscribe('payment-channel'); + + _channel!.bind( + 'paid-event-$idUser', + (event) async { + print("Event? ini apaan dah ${event!.data!}"); + if (mounted) { + final status = jsonDecode(event.data!); + final newStatusTransaction = status['status_code']; + final newStatusMessage = status['message']; + + if (newStatusMessage.contains("Berhasil !") && + newStatusTransaction == '200') { + await deleteCourse(); + if (mounted) { + setState(() { + statusTransaction = newStatusTransaction; + statusMessage = newStatusMessage; + }); + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => SuccessPaidCourse(), + ), + (route) => false, + ); + } + } else if (newStatusMessage.contains("Dibatalkan") && + newStatusTransaction == '200') { + await deleteCourse(); + + if (mounted) { + setState(() { + statusTransaction = newStatusTransaction; + statusMessage = newStatusMessage; + }); + Navigator.pop(context); + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.background, + elevation: 0.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: EdgeInsets.fromLTRB(12, 26, 22, 15), + content: Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(14)), + child: Text( + textAlign: TextAlign.center, + "Transaksi berhasil dibatalkan", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ); + } + } + } + }, + ); + } + + @override + void initState() { + initPusher(); + super.initState(); + } + + @override + void dispose() { + if (remainingTime != null) { + saveRemainingTime(remainingTime!); + } + super.dispose(); + } + + Widget _buildBatasBayarHistory() { + List<DetailOrderModel> detailOrder = + Provider.of<payProv.PaymentsProvider>(context).detailOrder; + var orders = Provider.of<OrderProvider>(context, listen: false).orders; + var selected = Provider.of<OrderProvider>(context); + selected.selectedThumbnail = orders[0].imageUrl; + selected.selectedTitle = orders[0].title; + selected.selectedInstructor = orders[0].instructor; + PageProvider pageProvider = Provider.of<PageProvider>(context); + + Widget mandiriPay() { + return detailOrder[0].bankName == 'mandiri' + ? Column( + children: [ + Row( + children: [ + Text( + detailOrder[0].billerCode.toString(), + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: detailOrder[0].billerCode.toString())) + .then( + (_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Berhasil Menyalin Kode Pembayaran'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + ], + ) + : Row(); + } + + Widget indomartPay() { + return detailOrder[0].bankName == 'indomaret' + ? Column( + children: [ + Row( + children: [ + Text( + detailOrder[0].merchantId!, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData( + ClipboardData(text: detailOrder[0].merchantId!)) + .then( + (_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text('Berhasil Menyalin Kode Merchant'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + ), + ], + ), + ], + ) + : Row(); + } + + return Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0.0, + centerTitle: true, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + 'Menunggu Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: Theme.of(context).colorScheme.onBackground, + size: getProportionateScreenWidth(17), + ), + onPressed: () { + pageProvider.currentIndex == 0; + Navigator.pushAndRemoveUntil( + context, + CustomNavigatorPop(child: HomeScreen()), + (route) => false, + ); + }, + ), + ), + body: SingleChildScrollView( + child: Consumer<payProv.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == payProv.Process.loading) { + return Padding( + padding: const EdgeInsets.all(20), + child: Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: getProportionateScreenHeight(40)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(250), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(30)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(170), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: Colors.white, + ), + ), + ], + ), + ), + ); + } else if (state.state == payProv.ResultState.gagal && + state.stateProcess == payProv.Process.uninitialized) { + return Center( + child: Text('Gagal Mengambil Pesanan'), + ); + } else if (state.state == payProv.ResultState.success && + state.stateProcess == payProv.Process.uninitialized) { + return Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(8)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: <Widget>[ + SizedBox(height: getProportionateScreenHeight(10)), + Center( + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: secondaryColor.withOpacity(0.2), + spreadRadius: 1, + blurRadius: 2, + offset: + Offset(0, getProportionateScreenHeight(4)), + ), + ], + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10), + vertical: getProportionateScreenHeight(15), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Batas Waktu Pembayaran', + style: thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DateFormat('E, d MMM y H:m WIB').format( + detailOrder[0].transactionTimeLimit), + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + Container( + decoration: BoxDecoration( + color: sevenColor, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(3), + vertical: getProportionateScreenHeight(2), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon( + Icons.access_time, + color: baruTextutih, + size: getProportionateScreenWidth(14), + ), + SizedBox( + width: + getProportionateScreenWidth(2)), + CountdownTimer( + endTime: DateTime.now() + .add(Duration(hours: 24)) + .millisecondsSinceEpoch, + widgetBuilder: + (_, CurrentRemainingTime? time) { + if (time == null) { + return Text( + '00:00:00', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: + getProportionateScreenWidth( + 10), + color: baruTextutih, + ), + ); + } else { + remainingTime = Duration( + hours: time.hours ?? 0, + minutes: time.min ?? 0, + seconds: time.sec ?? 0, + ); + return Text( + '${time.hours}:${time.min}:${time.sec}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: + getProportionateScreenWidth( + 10), + color: baruTextutih, + ), + ); + } + }, + ), + ], + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Text( + "Kursus", + style: thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: orders.map((e) { + return listCourse( + imageUrl: e.imageUrl, + instructor: e.instructor, + title: e.title, + price: e.price, + discountPrice: e.discountPrice, + ); + }).toList(), + ), + Text( + 'Metode Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + detailOrder[0].bankName == 'mandiri' + ? 'Mandiri Virtual Account' + : detailOrder[0].bankName == 'permata' + ? 'Permata Virtual Account' + : detailOrder[0].bankName == 'bni' + ? 'BNI Virtual Account' + : detailOrder[0].bankName == 'bca' + ? 'BCA Virtual Account' + : detailOrder[0].bankName == + 'indomaret' + ? 'Indomaret' + : 'Alfamart', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + Container( + width: getProportionateScreenWidth(50), + height: getProportionateScreenWidth(17), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + fit: BoxFit.fill, + image: detailOrder[0].bankName == + 'mandiri' + ? AssetImage( + "assets/images/mandiri.png") + : detailOrder[0].bankName == 'permata' + ? AssetImage( + "assets/images/permata.png") + : detailOrder[0].bankName == 'bni' + ? AssetImage( + "assets/images/bni.png") + : detailOrder[0].bankName == + 'bca' + ? AssetImage( + "assets/images/bca.png") + : detailOrder[0] + .bankName == + 'alfamart' + ? AssetImage( + "assets/images/alfamart.png") + : AssetImage( + "assets/images/indomaret.png"), + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + detailOrder[0].bankName == 'alfamart' || + detailOrder[0].bankName == 'indomaret' + ? 'Nomor Pesanan' + : 'Nomor Virtual Akun', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + detailOrder[0].virtualNumber ?? 'ABCD', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: + detailOrder[0].virtualNumber!)) + .then( + (_) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text(detailOrder[0] + .bankName == + 'alfamart' || + detailOrder[0].bankName == + 'indomaret' + ? 'Berhasil Menyalin Nomor Pesanan' + : 'Berhasil Menyalin Nomor Virtual Akun'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: + getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + detailOrder[0].bankName == 'indomaret' + ? Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(16), + bottom: getProportionateScreenHeight(8), + ), + child: Text( + 'Kode Merchant', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + ) + : SizedBox.shrink(), + detailOrder[0].bankName == 'mandiri' + ? Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(16), + bottom: getProportionateScreenHeight(8), + ), + child: Text( + 'Kode Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + ) + : SizedBox.shrink(), + indomartPay(), + mandiriPay(), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Total Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(detailOrder[0].totalPayment))}', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + showModalBottomSheet( + backgroundColor: Theme.of(context) + .colorScheme + .background, + elevation: 0.0, + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (context) { + return BottomSheetDetail(); + }, + ); + }, + child: Text( + "Detail Pembayaran", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: + getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + children: [ + SizedBox( + height: + getProportionateScreenHeight(24)), + DefaultButtonPayment( + text: 'Ubah Metode Pembayaran', + weight: semiBold, + press: () { + state.selectedIdOrders = ""; + Navigator.pop(context); + }, + width: 320, + height: 44, + ), + SizedBox( + height: + getProportionateScreenHeight(12)), + SizedBox( + width: getProportionateScreenWidth(320), + height: getProportionateScreenHeight(38), + child: TextButton( + onPressed: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + elevation: 0.0, + backgroundColor: + Theme.of(context) + .colorScheme + .background, + contentPadding: + EdgeInsets.symmetric( + vertical: + getProportionateScreenHeight( + 20), + horizontal: + getProportionateScreenWidth( + 10), + ), + actionsPadding: EdgeInsets.only( + right: + getProportionateScreenWidth( + 10), + bottom: + getProportionateScreenHeight( + 12), + ), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular( + getProportionateScreenWidth( + 4)), + ), + content: Text( + 'Apakah Anda yakin ingin membatalkan transaksi?'), + actions: [ + GestureDetector( + child: Text( + 'Ya', + style: TextStyle( + color: primaryColor, + fontSize: + getProportionateScreenWidth( + 11), + ), + ), + onTap: () async { + state.selectedIdOrders = + ""; + Navigator.of(context) + .pop(); + CancelPaymentService + cancelPaymentService = + CancelPaymentService(); + + setState(() { + isLoading = true; + }); + + await cancelPaymentService + .cancelPayment( + detailOrder[0] + .idOrder + .toString()); + + await Future.delayed( + Duration(seconds: 2)); + + setState(() { + isLoading = false; + }); + }, + ), + SizedBox( + width: + getProportionateScreenWidth( + 5)), + GestureDetector( + child: Text( + 'Tidak', + style: TextStyle( + color: primaryColor, + fontSize: + getProportionateScreenWidth( + 11), + ), + ), + onTap: () { + Navigator.of(context) + .pop(); + }, + ), + ], + ); + }, + ); + }, + child: isLoading + ? Container( + width: + getProportionateScreenWidth( + 15), + height: + getProportionateScreenHeight( + 13), + child: + CircularProgressIndicator( + color: primaryColor, + ), + ) + : Text( + "Batalkan Transaksi", + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + fontWeight: semiBold, + color: primaryColor, + letterSpacing: 0.5, + ), + ), + style: TextButton.styleFrom( + foregroundColor: Theme.of(context) + .colorScheme + .background, + shape: RoundedRectangleBorder( + side: + BorderSide(color: primaryColor), + borderRadius: BorderRadius.circular( + getProportionateScreenWidth(5), + ), + ), + backgroundColor: Colors.transparent, + ), + ), + ), + ], + ), + ], + ), + ], + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(4), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 4), + ), + ], + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + padding: EdgeInsets.only( + top: getProportionateScreenHeight(15), + bottom: getProportionateScreenHeight(7), + left: getProportionateScreenWidth(18), + ), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + boxShadow: [ + BoxShadow( + color: secondaryColor.withOpacity(0.2), + spreadRadius: 0, + blurRadius: 2, + offset: Offset( + 0, getProportionateScreenHeight(4)), + ), + ], + ), + child: Text( + 'Cara Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(5), + ), + child: detailOrder[0].bankName == 'indomaret' || + detailOrder[0].bankName == 'alfamart' + ? BarBatasBayar(detailOrder[0].bankName!) + : TabBarBatasBayar(), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(30)), + ], + ), + ); + } else { + return Center( + child: Text('Terjadi Kesalahan'), + ); + } + }, + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return _buildBatasBayarHistory(); + } + + Widget listCourse({ + String? imageUrl, + String? title, + String? instructor, + String? price, + String? discountPrice, + int? totalPrices, + }) { + return Container( + padding: EdgeInsets.symmetric(vertical: getProportionateScreenHeight(9)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(60), + height: getProportionateScreenHeight(30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + image: NetworkImage(imageUrl ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg'), + fit: BoxFit.cover, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/checkout/checkout_cart_coupon_page.dart b/lib/screens/checkout/checkout_cart_coupon_page.dart new file mode 100644 index 0000000..1beea6b --- /dev/null +++ b/lib/screens/checkout/checkout_cart_coupon_page.dart @@ -0,0 +1,384 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/checkout/components/course_list.dart'; +import 'package:initial_folder/screens/checkout/detail_zero_payment.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/order_provider.dart' as provOrder; + +import '../../providers/payments_provider.dart'; +import 'snap_payment_page.dart'; + +class CheckoutCartCouponPage extends StatefulWidget { + CheckoutCartCouponPage({ + Key? key, + required this.idCart, + this.potonganKupon, + this.discountHarga, + this.isKupon, + }) : super(key: key); + + final List<String> idCart; + final int? potonganKupon; + final int? discountHarga; + final bool? isKupon; + + @override + State<CheckoutCartCouponPage> createState() => _CheckoutCartCouponPageState(); +} + +class _CheckoutCartCouponPageState extends State<CheckoutCartCouponPage> { + final controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + final priceCoupon = Provider.of<TotalPriceProvider>(context).priceCoupon; + final totalPrice2 = Provider.of<TotalPriceProvider>(context).totalPrice; + final totalPrice3 = Provider.of<TotalPriceProvider>(context).totalPrices; + final typeCoupon = Provider.of<TotalPriceProvider>(context).typeCoupon; + final finalPriceCoupon = + Provider.of<TotalPriceProvider>(context).finalPriceCoupon; + final potonganKupon2 = + Provider.of<TotalPriceProvider>(context).potonganKupon; + + Widget bottomNav(String totalPrice) { + String totalHarga = + "Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(totalPrice == '0' ? widget.discountHarga : double.parse(totalPrice))}"; + + return Container( + width: double.infinity, + height: getProportionateScreenHeight(64), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.onPrimary.withOpacity(0.1), + spreadRadius: 4, + blurRadius: 15, + offset: Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + SizedBox(width: 13), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(6)), + Text( + "Total", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenHeight(12), + fontWeight: semiBold, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + if (widget.isKupon == null) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(finalPriceCoupon! < 50000 ? finalPriceCoupon + 5000 : finalPriceCoupon)}', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + letterSpacing: 0.23, + ), + ), + if (widget.isKupon == true) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(widget.discountHarga! < 50000 ? widget.discountHarga! + 5000 : widget.discountHarga)}', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + letterSpacing: 0.23, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + ], + ), + Spacer(), + GestureDetector( + onTap: () async { + print("Ini id cart? ${widget.idCart}"); + + if (totalHarga == 'Rp. 0') { + // Jika harga total adalah 0, arahkan ke halaman zero-payment + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DetailZeroPayment(idCart: widget.idCart), + ), + ); + } else { + // harga dan id transaksi untuk Snap Midtrans + String orderId = 'order-${DateTime.now().millisecondsSinceEpoch}'; + int grossAmount = int.parse(totalHarga.replaceAll('Rp. ', '').replaceAll(',', '')); + + // Persiapkan data_invoice berdasarkan pesanan + List<Map<String, dynamic>> dataInvoice = widget.idCart.map((id) { + return { + 'id_kursus': id, + 'title_kursus': 'Nama Kursus', + 'harga': grossAmount.toString(), + 'qty': '1', + }; + }).toList(); + + // Panggil Snap melalui provider + final paymentProvider = Provider.of<PaymentsProvider>(context, listen: false); + Map<String, String>? paymentResponse = await paymentProvider.startSnapPayment( + orderId: orderId, + grossAmount: grossAmount, + dataInvoice: dataInvoice, + ); + + if (paymentResponse != null) { + // Jika Snap berhasil, tampilkan halaman WebView Snap + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + transactionToken: paymentResponse['transactionToken'] ?? '', + orderId: orderId, + grossAmount: grossAmount, courseTitle: '', courseThumbnail: '', courseInstructor: '', courseId: '', + ), + ), + ); + } else { + print("Pembayaran gagal"); + } + } + }, + child: Container( + margin: EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(135), + height: getProportionateScreenHeight(35), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10), + ), + child: Center( + child: Text( + 'Pilih Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 1.7, + fontWeight: semiBold, + letterSpacing: 0.5, + color: baruTextutih, + ), + ), + ), + ), + ) + ], + ), + ); + } + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Checkout', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Detail Pesanan', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Consumer<provOrder.OrderProvider>( + builder: (context, state, _) { + print(state.orders.map((e) => e.idCourse)); + return Column( + children: [ + ...state.orders.map( + (order) => CourseList( + idCourse: order.idCourse, + title: order.title, + price: widget.isKupon == null + ? finalPriceCoupon.toString() + : (potonganKupon2 == 0 + ? priceCoupon.toString() + : (potonganKupon2! > totalPrice2! + ? finalPriceCoupon.toString() + : totalPrice3.toString())), + discountPrice: order.discountPrice, + imageUrl: order.imageUrl, + ), + ), + ], + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + 'Rincian Harga', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + children: [ + Text( + 'Subtotal Harga Kursus', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Spacer(), + if (widget.isKupon == null) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(priceCoupon!)}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + if (widget.isKupon == true) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(finalPriceCoupon == 0 ? priceCoupon : (potonganKupon2! > totalPrice2! ? finalPriceCoupon : totalPrice3))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Text( + 'Biaya Layanan', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Spacer(), + Text( + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(finalPriceCoupon! < 5000 ? "5000" : "0"))}', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Text( + 'Potongan Kupon', + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Spacer(), + Text( + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(typeCoupon != "3" ? widget.potonganKupon.toString() : (priceCoupon! - widget.potonganKupon!).toString()))}', + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + 'Total Bayar', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + if (widget.isKupon == null) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(finalPriceCoupon! < 50000 ? finalPriceCoupon + 5000 : finalPriceCoupon)}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + if (widget.isKupon == true) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(widget.discountHarga! < 50000 ? widget.discountHarga! + 5000 : widget.discountHarga)}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ], + ), + Row( + children: [ + Text(''), + Spacer(), + if (widget.isKupon == null) + Text( + (int.parse(Provider.of<provOrder.OrderProvider>(context) + .totalPrice!) != + 0 && + int.parse( + Provider.of<provOrder.OrderProvider>(context).totalPrice!) < 50000) || + finalPriceCoupon! < 50000 + ? "+ admin Rp 5000" + : "", + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + if (widget.isKupon != null) + Text( + (widget.discountHarga != 0 && widget.discountHarga! < 50000) + ? "+ admin Rp 5000" + : "", + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + ], + ), + ], + ), + ), + ), + bottomNavigationBar: bottomNav( + (int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice as String)).toString(), + ), + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/checkout/checkout_cart_page.dart b/lib/screens/checkout/checkout_cart_page.dart new file mode 100644 index 0000000..ae782a9 --- /dev/null +++ b/lib/screens/checkout/checkout_cart_page.dart @@ -0,0 +1,296 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/order_provider.dart' as provOrder; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/screens/checkout/components/course_list.dart'; +import 'package:initial_folder/screens/checkout/detail_zero_payment.dart'; +import 'package:initial_folder/screens/checkout/snap_payment_page.dart'; // Import SnapPaymentPage +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class CheckoutCartPage extends StatefulWidget { + CheckoutCartPage({ + Key? key, + required this.idCart, + this.potonganKupon, + this.discountHarga, + this.isCart, + this.isDetailCourse, + }) : super(key: key); + + final List<String> idCart; + final int? potonganKupon; + final int? discountHarga; + final bool? isCart; + final bool? isDetailCourse; + + @override + State<CheckoutCartPage> createState() => _CheckoutCartPageState(); +} + +class _CheckoutCartPageState extends State<CheckoutCartPage> { + final controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + final subTotal = Provider.of<TotalPriceProvider>(context).subTotal; + final paymentProvider = Provider.of<PaymentsProvider>(context, listen: false); + final themeProvider = Provider.of<ThemeProvider>(context); + + Widget bottomNav(String totalPrice) { + String totalHarga = + "Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(totalPrice == '0' ? widget.discountHarga : double.parse(totalPrice))}"; + + return Container( + width: double.infinity, + height: getProportionateScreenHeight(64), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.onPrimary.withOpacity(0.1), + spreadRadius: 4, + blurRadius: 15, + offset: Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + SizedBox(width: 13), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(6)), + Text( + "Total", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenHeight(12), + fontWeight: semiBold, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(Provider.of<provOrder.OrderProvider>(context).totalPrice as String == "0" ? (widget.discountHarga! < 50000 ? widget.discountHarga! + 5000 : widget.discountHarga) : (int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice!) == widget.discountHarga ? (int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice!) < 50000 ? int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice!) + 5000 : int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice as String)) : int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice as String)))}', + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + fontWeight: bold, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + ], + ), + Spacer(), + GestureDetector( + onTap: () async { + String orderId = 'order-${DateTime.now().millisecondsSinceEpoch}'; + int grossAmount = Provider.of<provOrder.OrderProvider>(context, listen: false) + .orders + .fold(0, (sum, order) { + // Gunakan harga diskon jika tersedia, jika tidak ada gunakan harga asli + int harga = (order.discountPrice != null && order.discountPrice != "0") + ? int.parse(order.discountPrice) + : int.parse(order.price); + + // Tambahkan biaya admin jika harga di bawah 50.000 + return sum + (harga < 50000 ? harga + 5000 : harga); + }); + + // Persiapkan data_invoice berdasarkan kursus yang ada di keranjang + List<Map<String, dynamic>> dataInvoice = []; + Provider.of<provOrder.OrderProvider>(context, listen: false).orders.forEach((order) { + dataInvoice.add({ + 'id_kursus': order.idCourse.toString(), + 'title_kursus': order.title, + // Gunakan harga diskon jika tersedia, jika tidak gunakan harga asli + 'harga': (order.discountPrice != null && order.discountPrice != "0") + ? order.discountPrice.toString() + : order.price.toString(), + 'qty': '1', + }); + }); + + // Panggil Snap melalui provider + Map<String, String>? paymentResponse = await paymentProvider.startSnapPayment( + orderId: orderId, + grossAmount: grossAmount, + dataInvoice: dataInvoice, + ); + + if (paymentResponse != null) { + // Snap berhasil dibuka, navigasi ke SnapPaymentPage untuk menampilkan WebView + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + transactionToken: paymentResponse['transactionToken'] ?? '', + orderId: orderId, + grossAmount: grossAmount, courseTitle: '', courseThumbnail: '', courseInstructor: '', courseId: '', + ), + ), + ); + } else { + print("Pembayaran gagal"); + } + }, + + child: Container( + margin: EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(135), + height: getProportionateScreenHeight(35), + decoration: BoxDecoration( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Pilih Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 1.7, + fontWeight: semiBold, + letterSpacing: 0.5, + color: baruTextutih, + ), + ), + ), + ), + ) + ], + ), + ); + } + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Checkout', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Detail Pesanan', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Consumer<provOrder.OrderProvider>( + builder: (context, state, _) { + return Column( + children: [ + ...state.orders.map( + (order) => widget.isDetailCourse != null + ? CourseList( + idCourse: order.idCourse, + title: order.title, + price: widget.isCart == null + ? order.price + : (order.discountPrice != null + ? order.discountPrice + : order.price), + discountPrice: order.discountPrice == null + ? order.discountPrice + : order.price, + imageUrl: order.imageUrl, + isDetailCourse: true, + ) + : CourseList( + idCourse: order.idCourse, + title: order.title, + price: widget.isCart == null + ? order.price + : (order.discountPrice != null + ? order.discountPrice + : order.price), + discountPrice: order.discountPrice == null + ? order.discountPrice + : order.price, + imageUrl: order.imageUrl, + ), + ), + ], + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + 'Rincian Harga', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + children: [ + Text( + 'Subtotal Harga Kursus', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Spacer(), + RichText( + text: TextSpan( + children: [ + if (widget.discountHarga.toString() != subTotal) + TextSpan( + text: + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(int.parse(subTotal!))}', + style: thirdTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + color: fourthColor, + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 2.8, + ), + ), + WidgetSpan( + child: SizedBox( + width: getProportionateScreenWidth(8))), + TextSpan( + text: + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(Provider.of<provOrder.OrderProvider>(context).totalPrice as String == "0" ? (widget.discountHarga! < 50000 ? widget.discountHarga! + 5000 : widget.discountHarga) : int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice as String))}', + style: thirdTextStyle.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + bottomNavigationBar: bottomNav( + (int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice as String)).toString(), + ), + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/checkout/checkout_coupon_page.dart b/lib/screens/checkout/checkout_coupon_page.dart new file mode 100644 index 0000000..4280a31 --- /dev/null +++ b/lib/screens/checkout/checkout_coupon_page.dart @@ -0,0 +1,415 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/get_it.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/detail_course_coupon_provider.dart'; +import 'package:initial_folder/screens/checkout/detail_zero_payment.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +import '../../providers/payments_provider.dart'; +import 'components/course_list_coupon.dart'; +import 'snap_payment_page.dart'; + +class CheckoutCouponPage extends StatefulWidget { + final idCourse; + final title; + final discountPrice; + final price; + final instructor; + final coupon; + + const CheckoutCouponPage( + {Key? key, + required this.idCourse, + required this.instructor, + required this.title, + required this.price, + required this.discountPrice, + required this.coupon}) + : super(key: key); + + @override + State<CheckoutCouponPage> createState() => _CheckoutCouponPageState(); +} + +class _CheckoutCouponPageState extends State<CheckoutCouponPage> { + final controller = TextEditingController(); + final provider = detailCouponGetIt<DetailCouponProvider>(); + + Future _showMessage(String text) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(22, 30, 22, 30), + content: Text( + text, + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + provider.getDetail(widget.idCourse, widget.coupon); + + return StreamBuilder<DataDetailCourseModel>( + stream: provider.detailStream, + builder: (context, AsyncSnapshot<DataDetailCourseModel> snapshot) { + final detail = snapshot.data; + + if (snapshot.hasError) { + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + case ConnectionState.none: + return Center( + child: Text( + 'Tidak ada koneksi', + style: thirdTextStyle, + ), + ); + case ConnectionState.active: + case ConnectionState.done: + final detail = snapshot.data; + return body( + widget.idCourse, + detail?.title ?? '', + detail?.price ?? '0', + detail?.discountPrice ?? '0', + detail?.thumbnail ?? '', + ); + } + } + return Container(); + }); + } + + Widget bottomNav(String discountPrice, String price) { + String total = discountPrice == '0' ? price : discountPrice; + + String totalPembayaran = + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price) - (double.parse(price) - double.parse(discountPrice)))}'; + + return Container( + width: double.infinity, + height: getProportionateScreenHeight(64), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: fourthColor)), + boxShadow: [ + BoxShadow( + offset: Offset(0, -2), + blurRadius: 50, + color: fourthColor.withOpacity(0.15)) + ]), + child: Row( + children: [ + SizedBox( + width: 13, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(6), + ), + Text( + "Total", + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenHeight(12), + letterSpacing: 1), + ), + SizedBox( + height: 2, + ), + Builder( + builder: (BuildContext context) { + double total = double.parse(price) - + (double.parse(price) - double.parse(discountPrice)); + if (total <= 50000) { + total += 5000; + } + return Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(total)}', + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + letterSpacing: 0.23), + ); + }, + ), + SizedBox( + height: getProportionateScreenHeight(2), + ), + ], + ), + Spacer(), + GestureDetector( + onTap: () async { + if (totalPembayaran == 'Rp. 0') { + // Jika total pembayaran adalah 0, arahkan ke halaman zero-payment + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DetailZeroPayment(), + ), + ); + } else { + // Dapatkan total harga dan id transaksi untuk Snap Midtrans + String orderId = 'order-${DateTime.now().millisecondsSinceEpoch}'; + int grossAmount = int.parse( + totalPembayaran.replaceAll('Rp. ', '').replaceAll(',', '') + ); + + // Persiapkan data_invoice untuk kursus yang dibeli + List<Map<String, dynamic>> dataInvoice = [ + { + 'id_kursus': widget.idCourse, + 'title_kursus': widget.title, + 'harga': grossAmount.toString(), + 'qty': '1', + } + ]; + + // Panggil Snap melalui provider + final paymentProvider = Provider.of<PaymentsProvider>(context, listen: false); + Map<String, String>? paymentResponse = await paymentProvider.startSnapPayment( + orderId: orderId, + grossAmount: grossAmount, + dataInvoice: dataInvoice, + ); + + if (paymentResponse != null) { + // Jika Snap berhasil, tampilkan halaman WebView Snap + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + transactionToken: paymentResponse['transactionToken'] ?? '', + orderId: orderId, + grossAmount: grossAmount, courseTitle: '', courseThumbnail: '', courseInstructor: '', courseId: '', + ), + ), + ); + } else { + print("Pembayaran gagal"); + } + } + }, + child: Container( + margin: EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(188), + height: getProportionateScreenHeight(41), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5), + ), + child: Center( + child: Text( + 'Lengkapi Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 1.7, + fontWeight: semiBold, + letterSpacing: 0.32, + color: Color(0xff050505), + ), + ), + ), + ), + ) + ], + ), + ); + } + + Widget body(String idCourse, String title, String price, String discountPrice, + String imageUrl) { + return Scaffold( + appBar: AppBar( + title: Text( + 'Checkout', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Column( + children: [ + Text( + 'Detail Pesanan', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + CourseListCoupon( + idCourse: idCourse, + title: title, + price: price, + discountPrice: discountPrice, + imageUrl: imageUrl), + SizedBox( + height: getProportionateScreenHeight(28), + ), + Text( + 'Rincian Harga', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + Row( + children: [ + Text( + 'Subtotal Harga Kursus', + style: primaryTextStyle.copyWith( + letterSpacing: 1, + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + Spacer(), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price))}', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox( + height: getProportionateScreenHeight(8), + ), + Row( + children: [ + Text( + 'Potongan Kupon', + style: primaryTextStyle.copyWith( + letterSpacing: 1, + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + Spacer(), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price) - double.parse(discountPrice))}', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox( + height: getProportionateScreenHeight(5), + ), + Divider( + color: tenthColor, + thickness: 0.5, + ), + SizedBox( + height: getProportionateScreenHeight(5), + ), + Row( + children: [ + Text( + 'Total', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + Builder( + builder: (BuildContext context) { + double total = double.parse(price) - + (double.parse(price) - double.parse(discountPrice)); + if (total <= 50000) { + total += 5000; + } else { + total = total; + } + return Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(total)}', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + color: primaryColor, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ); + }, + ), + ], + ), + Row( + children: [ + Text(''), + Spacer(), + Builder( + builder: (BuildContext context) { + double total = double.parse(price) - + (double.parse(price) - double.parse(discountPrice)); + return total <= 50000 + ? Text( + '+ admin Rp 5000', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + color: primaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ) + : Container(); + }, + ), + ], + ), + ], + ), + ), + ), + bottomNavigationBar: bottomNav(discountPrice, price), + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/checkout/checkout_detail_coupon.dart b/lib/screens/checkout/checkout_detail_coupon.dart new file mode 100644 index 0000000..78cec9d --- /dev/null +++ b/lib/screens/checkout/checkout_detail_coupon.dart @@ -0,0 +1,391 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/checkout/components/course_list.dart'; +import 'package:initial_folder/screens/checkout/detail_zero_payment.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/order_provider.dart' as provOrder; + +import '../../providers/payments_provider.dart'; +import 'snap_payment_page.dart'; + +class CheckoutDetailCoupon extends StatefulWidget { + CheckoutDetailCoupon({ + Key? key, + required this.idCart, + this.potonganKupon, + this.discountHarga, + this.isKupon, + }) : super(key: key); + + final List<String> idCart; + final int? potonganKupon; + final int? discountHarga; + final bool? isKupon; + + @override + State<CheckoutDetailCoupon> createState() => _CheckoutDetailCouponState(); +} + +class _CheckoutDetailCouponState extends State<CheckoutDetailCoupon> { + final controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + final priceCoupon = Provider.of<TotalPriceProvider>(context).priceCoupon; + final totalPrice2 = Provider.of<TotalPriceProvider>(context).totalPrice; + final totalPrice3 = Provider.of<TotalPriceProvider>(context).totalPrices; + final typeCoupon = Provider.of<TotalPriceProvider>(context).typeCoupon; + final finalPriceCoupon = + Provider.of<TotalPriceProvider>(context).finalPriceCoupon; + final potonganKupon2 = + Provider.of<TotalPriceProvider>(context).potonganKupon; + + Widget bottomNav(String totalPrice) { + String totalHarga = + "Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(totalPrice == '0' ? widget.discountHarga : double.parse(totalPrice))}"; + + return Container( + width: double.infinity, + height: getProportionateScreenHeight(64), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.onPrimary.withOpacity(0.1), + spreadRadius: 4, + blurRadius: 15, + offset: Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + SizedBox(width: 13), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(6)), + Text( + "Total", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenHeight(12), + fontWeight: semiBold, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + if (widget.isKupon == null) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(finalPriceCoupon! < 50000 ? finalPriceCoupon + 5000 : finalPriceCoupon)}', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + letterSpacing: 0.23, + ), + ), + if (widget.isKupon == true) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(widget.discountHarga! < 50000 ? widget.discountHarga! + 5000 : widget.discountHarga)}', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + letterSpacing: 0.23, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + ], + ), + Spacer(), + GestureDetector( + onTap: () async { + print("Ini id cart? ${widget.idCart}"); + + if (totalHarga == 'Rp. 0') { + // Jika total harga adalah 0, arahkan ke halaman zero-payment + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DetailZeroPayment(idCart: widget.idCart), + ), + ); + } else { + // Dapatkan total harga dan id transaksi untuk Snap Midtrans + String orderId = 'order-${DateTime.now().millisecondsSinceEpoch}'; + int grossAmount = int.parse( + totalHarga.replaceAll('Rp. ', '').replaceAll(',', '') + ); + + // Persiapkan data_invoice berdasarkan kursus yang dibeli + List<Map<String, dynamic>> dataInvoice = []; + widget.idCart.forEach((cartId) { + dataInvoice.add({ + 'id_kursus': cartId, // Sesuaikan dengan data yang relevan + 'title_kursus': 'Nama Kursus', // Nama kursus yang sesuai + 'harga': grossAmount.toString(), + 'qty': '1', // Sesuaikan jumlah jika diperlukan + }); + }); + + // Panggil Snap melalui provider + final paymentProvider = Provider.of<PaymentsProvider>(context, listen: false); + Map<String, String>? paymentResponse = await paymentProvider.startSnapPayment( + orderId: orderId, + grossAmount: grossAmount, + dataInvoice: dataInvoice, + ); + + if (paymentResponse != null) { + // Snap berhasil dibuka, arahkan ke halaman WebView Snap + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + transactionToken: paymentResponse[''] ?? '', + orderId: orderId, + grossAmount: grossAmount, courseTitle: '', courseThumbnail: '', courseInstructor: '', courseId: '', + ), + ), + ); + } else { + // Jika Snap gagal dibuka + print("Pembayaran gagal"); + } + } + }, + child: Container( + margin: EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(135), + height: getProportionateScreenHeight(35), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10), + ), + child: Center( + child: Text( + 'Pilih Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 1.7, + fontWeight: semiBold, + letterSpacing: 0.5, + color: baruTextutih, + ), + ), + ), + ), + ) + + ], + ), + ); + } + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Checkout', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Detail Pesanan', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Consumer<provOrder.OrderProvider>( + builder: (context, state, _) { + return Column( + children: [ + ...state.orders.map( + (order) => CourseList( + idCourse: order.idCourse, + title: order.title, + price: widget.isKupon == null + ? finalPriceCoupon.toString() + : (potonganKupon2 == 0 + ? finalPriceCoupon.toString() + : (potonganKupon2! > totalPrice2! + ? finalPriceCoupon.toString() + : totalPrice3.toString())), + discountPrice: priceCoupon.toString(), + imageUrl: order.imageUrl, + ), + ), + ], + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + 'Rincian Harga', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + children: [ + Text( + 'Subtotal Harga Kursus', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Spacer(), + if (widget.isKupon == null) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(priceCoupon!)}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + if (widget.isKupon == true) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(finalPriceCoupon == 0 ? priceCoupon : (potonganKupon2! > totalPrice2! ? finalPriceCoupon : totalPrice3))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Text( + 'Biaya Layanan', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Spacer(), + Text( + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(finalPriceCoupon! < 5000 ? "5000" : "0"))}', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Text( + 'Potongan Kupon', + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Spacer(), + Text( + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(typeCoupon != "3" ? widget.potonganKupon.toString() : (priceCoupon! - finalPriceCoupon).toString()))}', + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + 'Total Bayar', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + if (widget.isKupon == null) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(finalPriceCoupon! < 50000 ? finalPriceCoupon + 5000 : finalPriceCoupon)}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + if (widget.isKupon == true) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(widget.discountHarga! < 50000 ? widget.discountHarga! + 5000 : widget.discountHarga)}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ], + ), + Row( + children: [ + Text(''), + Spacer(), + if (widget.isKupon == null) + Text( + (int.parse(Provider.of<provOrder.OrderProvider>(context) + .totalPrice!) != + 0 && + int.parse( + Provider.of<provOrder.OrderProvider>(context) + .totalPrice!) < 50000) || + finalPriceCoupon! < 50000 + ? "+ admin Rp 5000" + : "", + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + if (widget.isKupon != null) + Text( + (widget.discountHarga != 0 && + widget.discountHarga! < 50000) + ? "+ admin Rp 5000" + : "", + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + ], + ), + ], + ), + ), + ), + bottomNavigationBar: bottomNav( + (int.parse(Provider.of<provOrder.OrderProvider>(context).totalPrice + as String)) + .toString(), + ), + ); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/checkout/checkout_page.dart b/lib/screens/checkout/checkout_page.dart new file mode 100644 index 0000000..57d140b --- /dev/null +++ b/lib/screens/checkout/checkout_page.dart @@ -0,0 +1,559 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/get_it.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/checkout/components/course_list.dart'; +import 'package:initial_folder/screens/checkout/detail_zero_payment.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/my_course/success_free_course.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/payments_provider.dart' + as paymentsProvider; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; + +import '../../providers/payments_provider.dart'; +import 'snap_payment_page.dart'; + +class CheckoutPage extends StatefulWidget { + final idCourse; + final title; + final discountPrice; + final price; + final instructor; + final thumbnail; + + const CheckoutPage({ + Key? key, + required this.idCourse, + required this.instructor, + required this.title, + required this.price, + required this.discountPrice, + required this.thumbnail, + }) : super(key: key); + + @override + State<CheckoutPage> createState() => _CheckoutPageState(); +} + +class _CheckoutPageState extends State<CheckoutPage> { + final controller = TextEditingController(); + final provider = detailGetIt<DetailProvider>(); + + Future _showMessage(String text) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(22, 30, 22, 30), + content: Text( + text, + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + provider.getDetail(widget.idCourse); + final selectedTotalPrice = Provider.of<TotalPriceProvider>(context); + // String? discountPrice; + + return StreamBuilder<DetailCourseModel>( + stream: provider.detailStream, + builder: (context, AsyncSnapshot<DetailCourseModel> snapshot) { + final detail = snapshot.data?.data[0][0]; + // discountPrice = detail?.discountPrice ?? '0'; + if (snapshot.hasError) { + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + break; + case ConnectionState.none: + Center( + child: Text( + 'Tidak ada koneksi', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.active: + final detail = snapshot.data?.data[0][0]; + // discountPrice = detail?.discountPrice ?? '0'; + body(widget.idCourse, detail?.title ?? '', detail?.price ?? '0', + detail?.discountPrice ?? '0', detail?.thumbnail ?? ''); + break; + case ConnectionState.done: + final detail = snapshot.data?.data[0][0]; + // discountPrice = detail?.discountPrice ?? '0'; + body(widget.idCourse, detail?.title ?? '', detail?.price ?? '0', + detail?.discountPrice ?? '0', detail?.thumbnail ?? ''); + break; + } + } + return body(widget.idCourse, detail?.title ?? '', detail?.price ?? '0', + detail?.discountPrice ?? '0', detail?.thumbnail ?? ''); + }, + ); + } + + Widget bottomNav(String discountPrice, String price) { + String total = discountPrice == '0' ? price : discountPrice; + paymentsProvider.PaymentsProvider pay = + Provider.of<paymentsProvider.PaymentsProvider>(context); + final selectedTotalPrice = Provider.of<TotalPriceProvider>(context); + + hargaTotalNavBar() { + String hargaTotal = + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price) - (double.parse(price) - double.parse(discountPrice)))}'; + return hargaTotal; + } + + showNotifDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, child) { + if (state.state == paymentsProvider.ResultState.gagal) { + return Container( + height: getProportionateScreenHeight(40), + width: getProportionateScreenWidth(15), + child: Center( + child: Text( + 'Anda sudah memiliki kursus ini', + style: primaryTextStyle.copyWith(fontSize: 12), + textAlign: TextAlign.center, + ), + ), + ); + } else { + return Text( + 'Erorr lain', + style: thirdTextStyle, + ); + } + }, + ), + ); + }, + ); + } + + Future _showDialogNotLogin(String teks) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum $teks', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + }, + child: Text( + 'Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + ], + ), + ); + } + + handleNotLoginFree( + String? id, + String? title, + String? thumb, + String? instr, + ) async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + print("ini bisa ya"); + if (await pay.freeCourse(int.parse(widget.idCourse))) { + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SuccessFreeCourse( + id: id, + thumbnail: thumb, + title: title, + instructor: instr, + ), + ), + ); + } else { + showNotifDialog(context); + } + } else { + String teks = 'memiliki kursus ini'; + return _showDialogNotLogin(teks); + } + } + + int totalPrice; + if (int.parse(discountPrice) != 0 && int.parse(discountPrice) < 50000) { + totalPrice = int.parse(discountPrice) + 5000; + } else { + if (int.parse(price) < 50000) { + totalPrice = int.parse(price) + 5000; + } else { + totalPrice = int.parse(price); + } + } + + return Container( + width: double.infinity, + height: getProportionateScreenHeight(64), + decoration: BoxDecoration( + border: Border(top: BorderSide(color: fourthColor)), + boxShadow: [ + BoxShadow( + offset: Offset(0, -2), + blurRadius: 50, + color: fourthColor.withOpacity(0.15), + ) + ], + ), + child: Row( + children: [ + SizedBox(width: 13), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(6)), + Text( + "Total", + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenHeight(12), + letterSpacing: 1), + ), + SizedBox(height: 2), + if (int.parse(price) < 50000 || int.parse(discountPrice) < 50000) + Text( + "Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(totalPrice)}", + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + letterSpacing: 0.23), + ) + else + Text( + "Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(discountPrice == "0" ? double.parse(price) : double.parse(discountPrice))}", + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 2.5, + letterSpacing: 0.23), + ), + SizedBox(height: getProportionateScreenHeight(2)), + ], + ), + Spacer(), + GestureDetector( + onTap: () async { + selectedTotalPrice.selectedTotalPrice = totalPrice; + + if (price == "0") { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DetailZeroPayment( + discountPrice: discountPrice, + price: price, + ), + ), + ); + } else { + // Dapatkan total harga dan id transaksi untuk Snap Midtrans + String orderId = 'order-${DateTime.now().millisecondsSinceEpoch}'; + int grossAmount = int.parse(price); + + // Persiapkan data invoice berdasarkan pesanan + List<Map<String, dynamic>> dataInvoice = [ + { + 'id_kursus': widget.idCourse, + 'title_kursus': widget.title, + 'harga': price.toString(), + 'qty': '1', + } + ]; + + // Panggil Snap melalui provider + final paymentProvider = Provider.of<PaymentsProvider>(context, listen: false); + bool success = (await paymentProvider.startSnapPayment( + orderId: orderId, + grossAmount: grossAmount, + dataInvoice: dataInvoice, + )) as bool; + + if (success) { + // Jika Snap berhasil, tampilkan halaman WebView Snap + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + transactionToken: '', + orderId: orderId, + grossAmount: grossAmount, courseTitle: '', courseThumbnail: '', courseInstructor: '', courseId: '', + ), + ), + ); + } else { + print("Pembayaran gagal"); + } + } + }, + child: Container( + margin: EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(188), + height: getProportionateScreenHeight(41), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5), + ), + child: Center( + child: Text( + 'Lengkapi Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockVertical! * 1.7, + fontWeight: semiBold, + letterSpacing: 0.32, + color: Color(0xff050505), + ), + ), + ), + ), + ) + ], + ), + ); + } + + Widget body(String idCourse, String title, String price, String discountPrice, + String imageUrl) { + int totalPrice; + if (int.parse(discountPrice) != 0 && int.parse(discountPrice) < 50000) { + totalPrice = int.parse(discountPrice) + 5000; + } else { + if (int.parse(price) < 50000) { + totalPrice = int.parse(price) + 5000; + } else { + totalPrice = int.parse(price); + } + } + return Scaffold( + appBar: AppBar( + title: Text( + 'Checkout', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Column( + children: [ + Text( + 'Detail Pesanan', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + CourseList( + idCourse: idCourse, + title: title, + price: price, + discountPrice: discountPrice, + imageUrl: imageUrl), + SizedBox(height: getProportionateScreenHeight(28)), + Text( + 'Rincian Harga', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + Row( + children: [ + Text( + 'Subtotal Harga Kursus', + style: primaryTextStyle.copyWith( + letterSpacing: 1, + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + Spacer(), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price))}', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + 'Potongan Kupon', + style: primaryTextStyle.copyWith( + letterSpacing: 1, + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + Spacer(), + discountPrice != "0" + ? Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price) - double.parse(discountPrice))}', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ) + : Text( + 'Rp. 0', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Divider( + color: tenthColor, + thickness: 0.5, + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Text( + 'Total', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + if (int.parse(price) < 50000 || + int.parse(discountPrice) < 50000) + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(totalPrice)}', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + color: primaryColor, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ) + else + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(discountPrice == "0" ? double.parse(price) : double.parse(discountPrice))}', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + color: primaryColor, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ], + ), + Row( + children: [ + Text(''), + Spacer(), + if (int.parse(price) < 50000 || + int.parse(discountPrice) < 50000 && + int.parse(discountPrice) != 0) + Text( + '+ admin Rp 5000', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + color: primaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ) + else + SizedBox(), + ], + ), + ], + ), + ), + ), + bottomNavigationBar: bottomNav(discountPrice, price), + ); + } + + @override + void dispose() { + controller.dispose(); + + super.dispose(); + } +} diff --git a/lib/screens/checkout/components/atm.dart b/lib/screens/checkout/components/atm.dart new file mode 100644 index 0000000..b4a7252 --- /dev/null +++ b/lib/screens/checkout/components/atm.dart @@ -0,0 +1,317 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:styled_text/styled_text.dart'; + +class ATMBNI extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Masukkan kartu anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: '2. Pilih Bahasa', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: '3. Masukkan <bold>PIN ATM</bold> Anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Pilih \"Menu Lainnya\"", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: "5. Pilih \"Transfer\"", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: + // '6. The billing information will appear on the payment validation page', + '6. Pilih Jenis rekening yang akan Anda gunakan', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: + // '7. If the information is correct, enter your password to proceed the payment', + '7. Pilih \"Virtual Account Billing\"', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '8. Your transaction will be processed', + text: '8. Masukkan nomor Virtual Account Anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class ATMBCA extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Masukkan kartu anda ATM dan PIN BCA anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Pada menu utama, pilih menu \"Transaksi lainnya\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: + "3. Pilih menu \"Transfer\" dan kemudian pilih \"BCA Virtual Account\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Masukkan no. BCA Virtual Account & klik \"Lanjutkan\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: "5. Periksa kembali rincian pembayaran anda, lalu pilih Ya", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class AtmMandiri extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Masukkan kartu anda ATM dan PIN anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Pada menu utama, pilih menu \"Bayar/Beli\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: + "3. Pilih menu \"Multi Payment\" (jika di layar belum tersedia tekan menu \"Lainnya\" dan pilih \"Multi Payment\") ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: + "4. Masukkan nomor Biller Code pada kode perusahaan & klik \"Benar\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: + "5. Masukkan kode pembayaran (kode pembayaran Mandiri billpayment anda)", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: + "6. Periksa kembali data transaksi anda dan selesaikan proses pembayaran.", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class AtmPermata extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Masukkan kartu ATM dan PIN anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Pada menu \"Transaksi Lainnya\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: + "3. Pilih menu \"Pembayaran\" kemudian Pilih \"Menu Pembayaran Lainnya\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Pilih menu \"VIRTUAL ACCOUNT\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: "5. Masukkan nomor \"VIRTUAL ACCOUNT\", dan tekan \"BENAR\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: + "6. Pilih rekening yang menjadi sumber dana yang akan didebet, lalu tekan YA untuk konfirmasi transaksi", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} diff --git a/lib/screens/checkout/components/bar_batas_bayar.dart b/lib/screens/checkout/components/bar_batas_bayar.dart new file mode 100644 index 0000000..4ff51de --- /dev/null +++ b/lib/screens/checkout/components/bar_batas_bayar.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:styled_text/styled_text.dart'; + +class BarBatasBayar extends StatelessWidget { + String storeName; + BarBatasBayar(this.storeName); + final TextStyle baris = thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(13), + ); + @override + Widget build(BuildContext context) { + String store = storeName == 'indomaret' ? 'Indomaret' : 'Alfamart'; + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(12), + vertical: getProportionateScreenHeight(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + text: '1. Datang ke<bold> $store</bold>', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: '2. Tunjukkan <bold>kode pembayaran</bold> ke kasir', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: + '3. Bayar dengan uang tunai sesuai dengan total pembayaran (sudah termasuk biaya layanan)', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: '4. Transaksi selesai, simpan bukti pembayaran Anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: 4) + ], + ), + ); + } +} diff --git a/lib/screens/checkout/components/bottom_sheet_detail.dart b/lib/screens/checkout/components/bottom_sheet_detail.dart new file mode 100644 index 0000000..3a6a071 --- /dev/null +++ b/lib/screens/checkout/components/bottom_sheet_detail.dart @@ -0,0 +1,263 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/order_provider.dart' as orderProvider; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart' + as userInfoProvider; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class BottomSheetDetail extends StatelessWidget { + Map<String, String> paymentMethod = { + 'echannel': 'Bank Transfer', + 'bank_transfer': 'Bank Transfer', + 'credit_card': 'Kartu Kredit', + 'gopay': 'GoPay', + 'cstore': 'Gerai' + }; + + Widget listCourse({String? title, String? instructor, String? price}) { + return Container( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + // "428 Menit Menjadi Pengusaha Sukses", + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + color: tenthColor, + ), + ), + SizedBox(height: 4), + Text( + 'Oleh $instructor', + // 'Oleh Farid Subkhan', + + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(9.5), + color: secondaryColor, + ), + ), + ], + ), + ), + Flexible( + flex: 3, + child: Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price!))}', + // 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(50000.0)}', + + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + color: tenthColor, + ), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + var dataOrder = + Provider.of<PaymentsProvider>(context, listen: false).detailOrder; + var dataCourse = + Provider.of<orderProvider.OrderProvider>(context, listen: false).orders; + var dataUser = + Provider.of<userInfoProvider.UserInfoProvider>(context, listen: false) + .result; + return Container( + decoration: BoxDecoration( + // color: Colors.red, + borderRadius: BorderRadius.vertical(top: Radius.circular(20))), + foregroundDecoration: BoxDecoration( + // color: Colors.red, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: ListView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon(Icons.keyboard_arrow_down)), + Text('Detail Pembayaran') + ], + ), + Container( + height: 6, + color: Color(0xff181818), + ), + Container( + height: MediaQuery.of(context).size.height * 0.4, + child: ListView( + shrinkWrap: true, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Informasi Pembeli'), + Divider(), + Text( + 'Order ID', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf)), + ), + Text( + dataOrder[0].idOrder, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4)), + ), + SizedBox( + height: 15, + ), + Text( + 'Nama Lengkap', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf)), + ), + Text( + dataUser!.data[0].fullname!, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4)), + ), + SizedBox( + height: 15, + ), + Text( + 'Email', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf)), + ), + Text( + dataUser.data[0].email!, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4)), + ), + ], + ), + ), + Container( + height: 6, + color: Color(0xff181818), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Informasi Pembayaran'), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Metode Pembayaran', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffbfbfbf))), + Text(paymentMethod[dataOrder[0].paymentType]!, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Total Harga', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf))), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(dataOrder[0].totalPayment))}', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Potongan Kupon', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf))), + Text('Rp. 0', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Total Bayar', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffbfbfbf))), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(dataOrder[0].totalPayment))}', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + ], + ), + ), + Container( + height: 6, + color: Color(0xff181818), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Kursus Yang Dibeli'), + Divider(), + Column( + children: dataCourse.map((course) { + return listCourse( + title: course.title, + instructor: course.instructor, + price: course.discountPrice == "0" + ? course.price + : course.discountPrice, + ); + }).toList()), + // listCourse(), + // listCourse(), + // listCourse(), + // Text('Halo'), + // Text('Halo'), + // Text('Halo'), + // Text('Halo') + ], + ), + ) + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/checkout/components/bottom_sheet_history.dart b/lib/screens/checkout/components/bottom_sheet_history.dart new file mode 100644 index 0000000..ac65370 --- /dev/null +++ b/lib/screens/checkout/components/bottom_sheet_history.dart @@ -0,0 +1,262 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/order_provider.dart' as orderProvider; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart' + as userInfoProvider; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class BottomSheetHistory extends StatelessWidget { + Map<String, String> paymentMethod = { + 'echannel': 'Bank Transfer', + 'bank_transfer': 'Bank Transfer', + 'credit_card': 'Kartu Kredit', + 'gopay': 'GoPay', + 'cstore': 'Gerai' + }; + + Widget listCourse({String? title, String? instructor, String? price}) { + return Container( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + // "428 Menit Menjadi Pengusaha Sukses", + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + color: tenthColor, + ), + ), + SizedBox(height: 4), + Text( + 'Oleh $instructor', + // 'Oleh Farid Subkhan', + + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(9.5), + color: secondaryColor, + ), + ), + ], + ), + ), + Flexible( + flex: 3, + child: Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price!))}', + // 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(50000.0)}', + + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + color: tenthColor, + ), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + var dataOrder = + Provider.of<PaymentsProvider>(context, listen: false).detailOrder; + var dataCourse = + Provider.of<orderProvider.OrderProvider>(context, listen: false).orders; + var dataUser = + Provider.of<userInfoProvider.UserInfoProvider>(context, listen: false) + .result; + int totalPrices = Provider.of<TotalPriceProvider>(context).totalPrices!; + return Container( + decoration: BoxDecoration( + // color: Colors.red, + borderRadius: BorderRadius.vertical(top: Radius.circular(20))), + foregroundDecoration: BoxDecoration( + // color: Colors.red, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: ListView( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon(Icons.keyboard_arrow_down)), + Text('Detail Pembayaran') + ], + ), + Container( + height: 6, + color: Color(0xff181818), + ), + Container( + height: MediaQuery.of(context).size.height * 0.4, + child: ListView( + shrinkWrap: true, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Informasi Pembeli'), + Divider(), + Text( + 'Order ID', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf)), + ), + Text( + dataOrder[0].idOrder, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4)), + ), + SizedBox( + height: 15, + ), + Text( + 'Nama Lengkap', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf)), + ), + Text( + dataUser!.data[0].fullname!, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4)), + ), + SizedBox( + height: 15, + ), + Text( + 'Email', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf)), + ), + Text( + dataUser.data[0].email!, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4)), + ), + ], + ), + ), + Container( + height: 6, + color: Color(0xff181818), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Informasi Pembayaran'), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Metode Pembayaran', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffbfbfbf))), + Text(paymentMethod[dataOrder[0].paymentType]!, + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Total Harga', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf))), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(Provider.of<TotalPriceProvider>(context).totalPrices.toString()))}', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + SizedBox(height: 15), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Potongan Kupon', + style: primaryTextStyle.copyWith( + fontSize: 10, color: Color(0xffbfbfbf))), + Text('Rp. 0', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Total Bayar', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffbfbfbf))), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(Provider.of<TotalPriceProvider>(context).totalPrices.toString()))}', + style: primaryTextStyle.copyWith( + fontSize: 12, color: Color(0xffF4f4f4))), + ], + ), + ], + ), + ), + Container( + height: 6, + color: Color(0xff181818), + ), + // Container( + // padding: EdgeInsets.symmetric(horizontal: 15, vertical: 20), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text('Kursus Yang Dibeli'), + // Divider(), + // Column( + // children: dataCourse.map((course) { + // return listCourse( + // title: course.title, + // instructor: course.instructor, + // price: course.discountPrice); + // }).toList()), + // listCourse(), + // listCourse(), + // listCourse(), + // Text('Halo'), + // Text('Halo'), + // Text('Halo'), + // Text('Halo') + // ], + // ), + // ) + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/checkout/components/cardmonth.dart b/lib/screens/checkout/components/cardmonth.dart new file mode 100644 index 0000000..9fbd5b9 --- /dev/null +++ b/lib/screens/checkout/components/cardmonth.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class CardMonthInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + var masaController = newValue.text; + if (newValue.selection.baseOffset == 0) { + return newValue; + } + + var buffer = new StringBuffer(); + + for (int i = 0; i < masaController.length; i++) { + buffer.write(masaController[i]); + var nonZeroIndex = i + 1; + + if (nonZeroIndex % 2 == 0 && nonZeroIndex != masaController.length) { + buffer.write('/'); + } + } + + var string = buffer.toString(); + return newValue.copyWith( + text: string, + selection: new TextSelection.collapsed(offset: string.length)); + } +} diff --git a/lib/screens/checkout/components/cardnumber.dart b/lib/screens/checkout/components/cardnumber.dart new file mode 100644 index 0000000..85c2db2 --- /dev/null +++ b/lib/screens/checkout/components/cardnumber.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class CardNumberInputFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate( + TextEditingValue oldValue, TextEditingValue newValue) { + var text = newValue.text; + + if (newValue.selection.baseOffset == 0) { + return newValue; + } + + var buffer = new StringBuffer(); + for (int i = 0; i < text.length; i++) { + buffer.write(text[i]); + var nonZeroIndex = i + 1; + if (nonZeroIndex % 4 == 0 && nonZeroIndex != text.length) { + buffer.write(' '); // Add double spaces. + } + } + + var string = buffer.toString(); + return newValue.copyWith( + text: string, + selection: new TextSelection.collapsed(offset: string.length)); + } +} diff --git a/lib/screens/checkout/components/course_list.dart b/lib/screens/checkout/components/course_list.dart new file mode 100644 index 0000000..8e97819 --- /dev/null +++ b/lib/screens/checkout/components/course_list.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; + +class CourseList extends StatelessWidget { + const CourseList({ + Key? key, + required this.idCourse, + required this.title, + required this.price, + required this.discountPrice, + required this.imageUrl, + this.isDetailCourse, + }) : super(key: key); + + final String idCourse; + final String title; + final String price; + final String discountPrice; + final String imageUrl; + final bool? isDetailCourse; + + @override + Widget build(BuildContext context) { + print("Ini price ${price}"); + print("Ini discountPrice ${discountPrice}"); + return Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 10, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(70), + height: getProportionateScreenWidth(39), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(imageUrl), + ), + ), + ), + ], + )), + SizedBox(width: getProportionateScreenWidth(10)), + Expanded( + flex: 15, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + )), + Spacer(), + Flexible( + flex: 10, + child: Align( + alignment: Alignment.centerRight, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (isDetailCourse == null) + discountPrice == "0" + ? Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ) + : Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ) + else + discountPrice == "0" + ? Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ) + : Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price == "0" ? discountPrice : price))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + if (isDetailCourse == null) + if (discountPrice != "0" && discountPrice != price) + Text( + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(discountPrice))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + decoration: TextDecoration.lineThrough, + color: fourthColor, + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 2.7, + ), + ) + else + SizedBox.shrink() + else if (price != "0") + Text( + 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(discountPrice))}', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + decoration: TextDecoration.lineThrough, + color: fourthColor, + fontWeight: reguler, + fontSize: SizeConfig.blockHorizontal! * 2.7, + ), + ) + else + SizedBox.shrink(), + SizedBox(height: getProportionateScreenHeight(2)), + discountPrice != "0" && discountPrice != price + ? SizedBox() + : SizedBox(), + ], + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Divider(color: fourthColor), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ); + } +} diff --git a/lib/screens/checkout/components/course_list_coupon.dart b/lib/screens/checkout/components/course_list_coupon.dart new file mode 100644 index 0000000..8e8fe3e --- /dev/null +++ b/lib/screens/checkout/components/course_list_coupon.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class CourseListCoupon extends StatelessWidget { + final String idCourse; + final String title; + final String price; + final String discountPrice; + final String imageUrl; + const CourseListCoupon({ + Key? key, + required this.idCourse, + required this.title, + required this.price, + required this.discountPrice, + required this.imageUrl, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final priceCoupon = Provider.of<TotalPriceProvider>(context).priceCoupon; + return Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 10, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(70), + height: getProportionateScreenWidth(39), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage(imageUrl), + ), + ), + ), + ], + )), + SizedBox(width: getProportionateScreenWidth(10)), + Expanded( + flex: 15, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + )), + Spacer(), + Flexible( + flex: 10, + child: Align( + alignment: Alignment.centerRight, + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + discountPrice == "0" + ? Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price))}', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ) + : Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(discountPrice))}', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + // Text( + // 'Rp ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price))}', + // style: primaryTextStyle.copyWith( + // letterSpacing: 0.5, + // fontWeight: reguler, + // fontSize: getProportionateScreenWidth(12), + // ), + // ), + SizedBox( + height: getProportionateScreenHeight(2), + ), + priceCoupon.toString() != "0" + ? Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(priceCoupon.toString()))}', + style: primaryTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + color: secondaryColor, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ) + : SizedBox(), + ], + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Divider(color: fourthColor), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ); + } +} diff --git a/lib/screens/checkout/components/field_kupon.dart b/lib/screens/checkout/components/field_kupon.dart new file mode 100644 index 0000000..21d3ca0 --- /dev/null +++ b/lib/screens/checkout/components/field_kupon.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; +import '../../../../theme.dart'; +import '../../../../size_config.dart'; + +class FieldKupon extends StatelessWidget { + final TextEditingController controler; + final IconButton? prefix; + const FieldKupon({Key? key, required this.controler, this.prefix}) + : super(key: key); + + @override + Widget build(BuildContext context) { + OutlineInputBorder outlineInputBorder = OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent), + gapPadding: 10, + ); + // width: SizeConfig.screenWidth * 0.9, + // height: 33, + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(15), + // border: Border.all( + // color: kSecondaryColor.withOpacity(0.5), + // width: 2, + + // return Scaffold( + // body: Container( + // height: 36, + // //padding: EdgeInsets.only(top: getProportionateScreenWidth(20)), + // child: TextField( + // controller: controler, + // cursorColor: secondaryColor, + // enabled: true, + // //obscureText: true, + // textAlignVertical: TextAlignVertical.center, + // style: TextStyle(fontSize: 12, color: Colors.white), + // onChanged: (value) => print(value), + // decoration: InputDecoration( + // //isDense: true, + // contentPadding: EdgeInsets.only(top: 10, left: 15), + // filled: true, + // fillColor: Colors.transparent, + // enabledBorder: outlineInputBorder, + // focusedBorder: outlineInputBorder, + // border: outlineInputBorder, + // hintText: "Masukkan kode kupon", + // hintStyle: primaryTextStyle.copyWith( + // fontSize: 12, + // color: fourthColor, + // fontWeight: reguler, + // letterSpacing: 0.5), + // ), + // ), + // ), + + // // SizedBox(height: getProportionateScreenHeight(16)), + // ); + return Column( + children: [ + Container( + height: getProportionateScreenHeight(32), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor.withOpacity(0.9) + : secondaryColor.withOpacity(0.2), + ), + child: TextField( + controller: controler, + cursorColor: Theme.of(context).colorScheme.onPrimary, + enabled: true, + textAlignVertical: TextAlignVertical.center, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onPrimary, + ), + onChanged: (value) => print(value), + decoration: InputDecoration( + prefix: prefix, + contentPadding: EdgeInsets.only( + top: getProportionateScreenHeight(10), + left: getProportionateScreenWidth(15), + ), + filled: true, + fillColor: Colors.transparent, + enabledBorder: outlineInputBorder, + focusedBorder: outlineInputBorder, + border: outlineInputBorder, + hintText: "Masukkan kode kupon", + hintStyle: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih.withOpacity(0.5) + : Colors.grey, + fontWeight: reguler, + letterSpacing: 0.5, + ), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(16)), + ], + ); + } +} diff --git a/lib/screens/checkout/components/internet_banking.dart b/lib/screens/checkout/components/internet_banking.dart new file mode 100644 index 0000000..b5a0cfe --- /dev/null +++ b/lib/screens/checkout/components/internet_banking.dart @@ -0,0 +1,337 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:styled_text/styled_text.dart'; + +class InternetBankBNI extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Masuk ke https://ibank.bni.co.id', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: '2. Masukkan User ID dan Password', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: + "3. Pilih menu \"Transfer\", lalu pilih \"Tambah Rekening Favorit\". Jika menggunakan Desktop. tambah rekening pada menu \"Transaksi\" kemudian \"Atur Rekening Tujuan\" lalu pilih \"Tambah Rekening Tujuan\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Masukkan nomor Virtual Account", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: + "5. Masukkan Kode Otentikasi dan Nomor Rekening berhasil ditambahkan", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: + // '6. The billing information will appear on the payment validation page', + "6. Pilih menu \"Transfer\", lalu pilih \"Transfer Antar Rekening BNI\", pilih \"Rekening Tujuan\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: + // '7. If the information is correct, enter your password to proceed the payment', + "7. Pilih Rekening Debit", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '8. Your transaction will be processed', + text: "8. Masukkan jumlah pembayaran sesuai tagihan", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '8. Your transaction will be processed', + text: "9. Masukkan Kode Otentikasi", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class InternetBankBCA extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Login pada aplikasi KlikBCA, masukkan user ID & PIN.', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: + "2. Pilih \"Transfer Dana\", kemudian pilih \"Transfer ke BCA Virtual Account\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: "3. Masukkan no. BCA Virtual Account & klik \"Lanjutkan\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: + "4. Pastikan data yang dimasukkan sudah benar, dan Input \"Respon KeyBCA\", lalu klik \"Kirim\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class InternetBankMandiri extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Lakukan Login ke Internet Banking Mandiri kamu.', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: + "2. Pada menu utama, pilih menu \"Bayar\" lalu pilih menu \"Multi Payment\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: + "3. Pilih akun anda di bagian Dari Rekening, kemudian di Penyedia Jasa ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: + "4. Masukkan kode pembayaran (kode pembayaran Mandiri billpayment kamu), dan klik \"Lanjutkan\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: + "5. Periksa kembali nama perusahaan, nomor pesanan, dan jumlah pembayaran anda ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "6. Selesaikan pembayaran dengan menggunakan Token Mandiri ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class InternetBankPermata extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Buka Website PermataNet', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Masukan User ID dan Password ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: "3. Pilih Pembayaran Tagihan ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Pilih Virtual Account ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "5. Masukkan 16 digit kode bayar ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "6. Masukkan nominal pembayaran ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "7. Muncul konfirmasi pembayaran ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "8. Masukan Mobile PIN", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} diff --git a/lib/screens/checkout/components/mobile_banking.dart b/lib/screens/checkout/components/mobile_banking.dart new file mode 100644 index 0000000..f992493 --- /dev/null +++ b/lib/screens/checkout/components/mobile_banking.dart @@ -0,0 +1,351 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:styled_text/styled_text.dart'; + +class MobileBankBNI extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: "1. Akses BNI Mobile Banking melalui handphone", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Masukkan User ID dan Password", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: + "3. Pilih menu \"Transfer\", lalu pilih \"Antar Rekening BNI\", pilih \"Input Rekening Baru\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Masukkan nomor Virtual Account", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '5. Enter the 16 digits <bold>virtual account number</bold>', + text: + "5.Di halaman konfirmasi, pastikan data transaksi sudah benar kemudian pilih \"Ya\"", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + text: + // '6. The billing information will appear on the payment validation page', + "6. Masukkan password anda ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class MobileBankBCA extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Lakukan log in pada aplikasi BCA mobile.', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Pilih \"m-BCA\" masukan kode akses m-BCA. ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: "3. Pilih \"m-Transfer\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Pilih \"BCA Virtual Account\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "5. Masukkan nomor BCA Virtual Account dan klik \"OK\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "6. Konfirmasi no virtual account dan rekening pendebetan ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: + "7. Periksa kembalian rincian pembayaran kamu, lalu klik \"Ya\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "8. Masukan pin m-BCA ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class MobileBankMandiri extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Lakukan Login ke Mandiri Online kamu', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Pada menu utama, pilih menu \"Bayar\"", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: "3. Lalu pilih menu \"Multi Payment\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. kemudian pilih Penyedia Jasa ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: + "5. Masukkan kode pembayaran [Kode pembayaran Mandiri billpayment], dan klik \"Lanjutkan\" ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: + "6. Periksa kembali data transaksi kamu dan selesaikan proses pembayaran ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} + +class MobileBankPermata extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + @override + Widget build(BuildContext context) { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(12)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + // text: '1. Open the <bold>BNI Mobile Banking</bold> app and login', + text: '1. Buka aplikasi PermataMobile X', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '2. Choose menu <bold>Transfer</bold>', + text: "2. Masukan Password ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '3. Choose menu <bold>Virtual Account Billing</bold>', + text: "3. Pilih Pembayaran Tagihan ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "4. Pilih Virtual Account ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "5. Masukan Nomor Virtual Account ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "6. Pilih Rekening", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "7. Masukkan nominal pembayaran ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "8. Muncul konfirmasi pembayaran ", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + Divider(color: Color(0xff2D2D2D), thickness: 0.5, height: 35), + StyledText( + // text: '4. Choose the bank account you want to use', + text: "9. Masukan Mobile PIN", + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ); + } +} diff --git a/lib/screens/checkout/components/tab_bar_batas_bayar.dart b/lib/screens/checkout/components/tab_bar_batas_bayar.dart new file mode 100644 index 0000000..d5ff049 --- /dev/null +++ b/lib/screens/checkout/components/tab_bar_batas_bayar.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/detail_order_model.dart'; +import 'package:initial_folder/providers/payments_provider.dart' as payProv; +import 'package:initial_folder/providers/tab_provider.dart'; +import 'package:initial_folder/screens/checkout/components/atm.dart'; +import 'package:initial_folder/screens/checkout/components/internet_banking.dart'; +import 'package:initial_folder/screens/checkout/components/mobile_banking.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class TabBarBatasBayar extends StatelessWidget { + TabBarBatasBayar({ + Key? key, + this.bank, + }) : super(key: key); + + final String? bank; + + @override + Widget build(BuildContext context) { + print("Apa hayoo ${bank}"); + List<DetailOrderModel> detailOrder = + Provider.of<payProv.PaymentsProvider>(context).detailOrder; + String? bankName = detailOrder.isNotEmpty ? detailOrder[0].bankName : null; + print("Apa hayoo2 ${bankName}"); + TabProvider tab = Provider.of<TabProvider>(context); + + Widget buildContent(int currentIndex) { + switch (currentIndex) { + case 0: + switch (bank ?? bankName) { + case 'bni': + return ATMBNI(); + case 'bca': + return ATMBCA(); + case 'mandiri': + case 'echannel': + return AtmMandiri(); + case 'permata': + return AtmPermata(); + default: + return SizedBox(); + } + case 1: + switch (bank ?? bankName) { + case 'bni': + return InternetBankBNI(); + case 'bca': + return InternetBankBCA(); + case 'mandiri': + case 'echannel': + return InternetBankMandiri(); + case 'permata': + return InternetBankPermata(); + default: + return SizedBox(); + } + case 2: + switch (bank ?? bankName) { + case 'bni': + return MobileBankBNI(); + case 'bca': + return MobileBankBCA(); + case 'mandiri': + case 'echannel': + return MobileBankMandiri(); + case 'permata': + return MobileBankPermata(); + default: + return SizedBox(); + } + default: + return SizedBox(); + } + } + + return Column( + children: [ + ExpansionTile( + title: Text( + 'ATM', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13)), + ), + children: [ + buildContent(0), + ], + ), + Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(8)), + height: getProportionateScreenHeight(3), + decoration: BoxDecoration( + color: secondaryColor.withOpacity(0.1), + boxShadow: [ + BoxShadow( + color: secondaryColor.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 1, + offset: Offset(0, 1), + ), + ], + ), + ), + ExpansionTile( + title: Text( + 'Internet Banking', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13)), + ), + children: [ + buildContent(1), + ], + ), + Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(8)), + height: getProportionateScreenHeight(3), + decoration: BoxDecoration( + color: secondaryColor.withOpacity(0.1), + boxShadow: [ + BoxShadow( + color: secondaryColor.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 1, + offset: Offset(0, 1), + ), + ], + ), + ), + ExpansionTile( + title: Text( + 'Mobile Banking', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13)), + ), + children: [ + buildContent(2), + ], + ), + ], + ); + } +} diff --git a/lib/screens/checkout/detail_zero_payment.dart b/lib/screens/checkout/detail_zero_payment.dart new file mode 100644 index 0000000..a3464d7 --- /dev/null +++ b/lib/screens/checkout/detail_zero_payment.dart @@ -0,0 +1,435 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/zero_price_model.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart' as userInfo; +import 'package:initial_folder/screens/checkout/batas_bayar.dart'; +import 'package:initial_folder/screens/checkout/success_paid_course.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class DetailZeroPayment extends StatelessWidget { + final String? discountPrice; + final String? price; + final List<String>? idCart; + const DetailZeroPayment( + {Key? key, this.idCart, this.discountPrice, this.price}) + : super(key: key); + + @override + Widget build(BuildContext context) { + Future<void> deleteCourse() async { + List<String> idCarts = idCart!; + + idCarts.forEach((element) async { + await Provider.of<CartProvider>(context, listen: false) + .deleteCart(element); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + }); + } + + var invoice = Provider.of<OrderProvider>(context, listen: false).invoice; + var totalPrice = + Provider.of<OrderProvider>(context, listen: false).totalPrice!; + var orders = Provider.of<OrderProvider>(context, listen: false).orders; + print(orders); + var zero = Provider.of<PaymentsProvider>(context).zeroPrice; + Widget bottomNav() { + return Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(90), + vertical: getProportionateScreenHeight(10)), + child: DefaultButton( + text: 'Lanjutkan', + press: () { + Provider.of<PaymentsProvider>(context, listen: false) + .zeroPayment(invoice, totalPrice.toString()); + deleteCourse(); + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => SuccessPaidCourse())); + }, + ), + ); + } + + Widget listCourse({String? title, String? instructor, String? price}) { + return Container( + padding: EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + color: tenthColor, + ), + ), + SizedBox(height: 4), + Text( + 'Oleh $instructor', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(9.5), + color: secondaryColor, + ), + ), + ], + ), + ), + Flexible( + flex: 3, + child: Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price!))}', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + color: tenthColor, + ), + ), + ), + ], + ), + ); + } + + var user = Provider.of<userInfo.UserInfoProvider>(context).result; + // var zero = Provider.of<PaymentsProvider>(context).zeroPrice; + return Scaffold( + //backgroundColor: Colors.black, + appBar: AppBar( + title: Text( + 'Checkout', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: Color(0xFF212121), + borderRadius: BorderRadius.circular(8), + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: 15, + horizontal: getProportionateScreenWidth(15)), + child: Text( + 'Informasi Pembeli', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + color: tenthColor, + ), + ), + ), + Divider( + color: Color(0xff2D2D2D), + thickness: 0.5, + height: 1), + Container( + padding: EdgeInsets.symmetric( + vertical: 10, + horizontal: getProportionateScreenWidth(15)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text( + // 'Order ID', + // style: primaryTextStyle.copyWith( + // letterSpacing: 0.5, + // fontWeight: reguler, + // fontSize: getProportionateScreenWidth(10), + // color: secondaryColor, + // ), + // ), + // Text( + // // state.detailOrder[0].idOrder, + // // zero!.data![0].orderId as String, + // '123', + // style: primaryTextStyle.copyWith( + // letterSpacing: 0.5, + // fontWeight: reguler, + // fontSize: getProportionateScreenWidth(12), + // color: tenthColor, + // ), + // ), + // SizedBox(height: 16), + Text( + 'Nama Lengkap', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + color: secondaryColor, + ), + ), + Text( + user!.data[0].fullname as String, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + color: tenthColor, + ), + ), + SizedBox(height: 16), + Text( + 'Email', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + color: secondaryColor, + ), + ), + Text( + user.data[0].email as String, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + color: tenthColor, + ), + ), + SizedBox(height: 12), + ], + ), + ), + ], + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + Container( + decoration: BoxDecoration( + color: Color(0xFF212121), + borderRadius: BorderRadius.circular(8), + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: 15, + horizontal: getProportionateScreenWidth(15)), + child: Text( + 'Informasi Pembeli', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + color: tenthColor, + ), + ), + ), + Divider( + color: Color(0xff2D2D2D), + thickness: 0.5, + height: 1), + Container( + padding: EdgeInsets.symmetric( + vertical: 15, + horizontal: getProportionateScreenWidth(15)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 20), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Harga', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + color: secondaryColor, + ), + ), + discountPrice != "0" + ? Text( + "Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(discountPrice!))}", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth( + 12), + color: tenthColor, + ), + ) + : Text( + "Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(price!))}", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth( + 12), + color: tenthColor, + ), + ), + ], + ), + SizedBox(height: 16), + Divider( + color: Color(0xff2D2D2D), + thickness: 0.5, + height: 1), + SizedBox(height: 16), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Bayar', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(12), + color: secondaryColor, + ), + ), + Text( + 'Rp. 0', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: + getProportionateScreenWidth(14), + color: tenthColor, + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + Container( + decoration: BoxDecoration( + color: Color(0xFF212121), + borderRadius: BorderRadius.circular(8), + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: 15, + horizontal: getProportionateScreenWidth(15)), + child: Text( + 'Kursus yang dibeli', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + color: tenthColor, + ), + ), + ), + Divider( + color: Color(0xff2D2D2D), + thickness: 0.5, + height: 1), + Container( + padding: EdgeInsets.symmetric( + vertical: 15, + horizontal: getProportionateScreenWidth(15)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: orders.map((e) { + return listCourse( + instructor: e.instructor, + title: e.title, + price: e.discountPrice); + }).toList(), + ), + ) + ], + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + bottomNav(), + SizedBox( + height: getProportionateScreenHeight(15), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/checkout/gopay/batas_bayar_gopay.dart b/lib/screens/checkout/gopay/batas_bayar_gopay.dart new file mode 100644 index 0000000..7b58ca8 --- /dev/null +++ b/lib/screens/checkout/gopay/batas_bayar_gopay.dart @@ -0,0 +1,377 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/screens/checkout/components/bottom_sheet_detail.dart'; +import 'package:initial_folder/screens/checkout/gopay/payment_instruction_gopay.dart'; +import 'package:initial_folder/screens/checkout/gopay/qr_code_gopay.dart'; +import 'package:initial_folder/screens/checkout/success_paid_course.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:pusher_client/pusher_client.dart'; + +class BatasBayarGopay extends StatefulWidget { + final List<String>? idCart; + BatasBayarGopay({this.idCart}); + @override + State<BatasBayarGopay> createState() => _BatasBayarGopayState(); +} + +class _BatasBayarGopayState extends State<BatasBayarGopay> { + Channel? _channel; + String? statusTransaction; + + Future<void> deleteCourse() async { + List<String> idCarts = widget.idCart ?? []; + + idCarts.forEach((element) async { + await Provider.of<CartProvider>(context, listen: false) + .deleteCart(element); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + }); + } + + Future<void> initPusher() async { + int? idUser = await UsersInfo().getIdUser(); + + PusherClient pusher = PusherClient( + '92060797e94ac7033edb', PusherOptions(cluster: 'ap1'), + autoConnect: false); + + pusher.connect(); + + // pusher.onConnectionStateChange((state) { + // print(state!.currentState); + // }); + // pusher.onConnectionError((error) { + // print(error); + // }); + + _channel = pusher.subscribe('payment-channel'); + + _channel!.bind('paid-event-$idUser', (event) { + if (mounted) { + final status = jsonDecode(event!.data!); + setState(() { + statusTransaction = status['status_code']; + if (statusTransaction == "201") { + print(status['message']); + print(widget.idCart); + deleteCourse(); + // Navigator.of(context).pushAndRemoveUntil( + // MaterialPageRoute( + // builder: (context) => SuccessPaidCourse(), + // ), + // (route) => false, + // ); + } + }); + } + }); + } + + @override + void initState() { + initPusher(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + var detailOrder = Provider.of<PaymentsProvider>(context).detailOrder; + return Scaffold( + appBar: AppBar( + title: Text( + 'Selesaikan Pembayaran', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: SingleChildScrollView( + child: Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: <Widget>[ + SizedBox(height: getProportionateScreenHeight(10)), + Center( + child: Column( + children: [ + Text( + 'Batas akhir pembayaran sampai', + style: primaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + // 'Rabu, 13 Oktober 2021 (Pukul 19:32)', + DateFormat('E, d MMM y (H:m)') + .format(detailOrder[0].transactionTimeLimit), + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + color: tenthColor, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + 'Segera selesaikan pembayaran atau pesananmu', + style: primaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Text( + 'akan dibatalkan secara otomatis', + style: primaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(30), + ), + Container( + decoration: BoxDecoration( + color: Color(0xFF212121), + borderRadius: BorderRadius.circular(8), + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Container( + padding: EdgeInsets.symmetric( + vertical: 16, + horizontal: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Metode Pembayaran', + style: primaryTextStyle.copyWith( + color: Colors.white, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox( + height: getProportionateScreenHeight(8), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'GoPay', + style: primaryTextStyle.copyWith( + color: Colors.white, + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + Container( + width: getProportionateScreenWidth(50), + height: getProportionateScreenWidth(17), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + fit: BoxFit.scaleDown, + image: AssetImage('assets/images/gopay2.png'), + ), + ), + ), + ], + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + Text( + 'Kode QR', + style: primaryTextStyle.copyWith( + color: Colors.white, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox( + height: getProportionateScreenHeight(8), + ), + Row( + children: [ + Icon(Icons.qr_code), + Spacer(), + GestureDetector( + onTap: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => QRCodeGopay())); + Navigator.pop(context); + }, + child: Text( + "Scan Kode QR", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + Text( + 'Total Pembayaran', + style: primaryTextStyle.copyWith( + color: Colors.white, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox( + height: getProportionateScreenHeight(8), + ), + Row( + children: [ + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(detailOrder[0].totalPayment))}', + style: primaryTextStyle.copyWith( + color: Colors.white, + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20))), + builder: (context) { + return BottomSheetDetail(); + }); + }, + child: Text( + "Detail Pembayaran", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + Text( + 'Status Pembayaran', + style: primaryTextStyle.copyWith( + color: Colors.white, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox( + height: getProportionateScreenHeight(8), + ), + Text( + statusTransaction == '201' || statusTransaction == null + ? 'Pending' + : 'Success', + style: primaryTextStyle.copyWith( + color: Color(0xffEDA923), + letterSpacing: 1, + fontWeight: medium, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + ), + ), + SizedBox( + height: getProportionateScreenHeight(24), + ), + DefaultButton( + text: 'Belanja kursus lainnya', + weight: reguler, + press: () { + Navigator.pushNamedAndRemoveUntil( + context, "/home", (r) => false); + }, + ), + SizedBox( + height: getProportionateScreenHeight(32), + ), + Container( + decoration: BoxDecoration( + color: Color(0xFF212121), + borderRadius: BorderRadius.circular(8), + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: 16, + horizontal: getProportionateScreenWidth(16)), + child: Text( + 'Cara Pembayaran', + style: secondaryTextStyle.copyWith( + color: tenthColor, + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + Divider( + color: Color(0xff2D2D2D), thickness: 0.5, height: 0.5), + PaymentInstructionGopay() + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(30), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/checkout/gopay/bayargopay.dart b/lib/screens/checkout/gopay/bayargopay.dart new file mode 100644 index 0000000..b05a7e6 --- /dev/null +++ b/lib/screens/checkout/gopay/bayargopay.dart @@ -0,0 +1,249 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/screens/checkout/components/bottom_sheet_detail.dart'; +import 'package:initial_folder/screens/checkout/gopay/payment_instruction_gopay.dart'; +import 'package:initial_folder/screens/checkout/gopay/qr_code_gopay.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +class BayarGopay extends StatelessWidget { + BayarGopay({this.idCart}); + + final List<String>? idCart; + + static String routeName = "/bayarGopay"; + + @override + Widget build(BuildContext context) { + Widget bottomNav() { + return DefaultButton( + text: 'Bayar dengan QRIS', + press: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => GopaySplashScreen(idCart: idCart), + ), + ); + }, + ); + } + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + 'Bayar Dengan QRIS', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + body: Consumer<PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == Process.loading) { + return SizedBox( + height: MediaQuery.of(context).size.height, + child: Center(child: CircularProgressIndicator())); + } else if (state.state == ResultState.gagal) { + return Center(child: Text('Terjadi Kesalahan')); + } else { + return SingleChildScrollView( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 10), + Container( + width: double.infinity, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: 15, + horizontal: + getProportionateScreenWidth(15)), + child: Text( + 'Informasi Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + color: Theme.of(context) + .colorScheme + .onBackground, + ), + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: + getProportionateScreenWidth(15)), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Order ID', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + fontFamily: "Poppins", + ), + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + state.detailOrder[0].idOrder, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth( + 12), + ), + ), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: state + .detailOrder[0].idOrder, + )).then( + (_) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Berhasil Menyalin Kode Pembayaran'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: + getProportionateScreenWidth( + 10), + fontWeight: reguler), + ), + ), + ], + ), + SizedBox( + height: + getProportionateScreenHeight(10)), + Text( + 'Total Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + fontFamily: "Poppins", + ), + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '').format(double.parse(state.detailOrder[0].totalPayment))}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: + getProportionateScreenWidth( + 12), + ), + ), + GestureDetector( + onTap: () { + showModalBottomSheet( + backgroundColor: + Theme.of(context) + .colorScheme + .background, + elevation: 0.0, + context: context, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (context) { + return BottomSheetDetail(); + }, + ); + }, + child: Text( + "Detail Pembayaran", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: + getProportionateScreenWidth( + 10), + fontWeight: reguler), + ), + ), + ], + ), + SizedBox( + height: + getProportionateScreenHeight(12)), + ], + ), + ), + ], + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(30)), + PaymentInstructionGopay(), + SizedBox(height: getProportionateScreenHeight(15)), + bottomNav(), + SizedBox(height: getProportionateScreenHeight(15)), + ], + ), + ), + ); + } + }, + ), + ); + } +} diff --git a/lib/screens/checkout/gopay/gopay_payment_confirmation.dart b/lib/screens/checkout/gopay/gopay_payment_confirmation.dart new file mode 100644 index 0000000..c540b4b --- /dev/null +++ b/lib/screens/checkout/gopay/gopay_payment_confirmation.dart @@ -0,0 +1,395 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_countdown_timer/current_remaining_time.dart'; +import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/screens/checkout/components/bottom_sheet_detail.dart'; +import 'package:initial_folder/screens/checkout/gopay/payment_instruction_gopay.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class GopayPaymentConfirmation extends StatelessWidget { + @override + Widget build(BuildContext context) { + var detailOrder = + Provider.of<PaymentsProvider>(context, listen: false).detailOrder; + var orders = Provider.of<OrderProvider>(context, listen: false).orders; + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + scrolledUnderElevation: 0.0, + title: Text( + 'Cara Pembayaran', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + body: Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(10)), + child: SingleChildScrollView( + child: Column( + children: [ + Container( + height: getProportionateScreenHeight(290), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + margin: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(9), + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10), + vertical: getProportionateScreenHeight(15), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Batas Waktu Pembayaran', + style: thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DateFormat('E, d MMM y H:m WIB') + .format(detailOrder[0].transactionTimeLimit), + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + Container( + decoration: BoxDecoration( + color: sevenColor, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(3), + vertical: getProportionateScreenHeight(2), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon( + Icons.access_time, + color: baruTextutih, + size: getProportionateScreenWidth(14), + ), + SizedBox(width: getProportionateScreenWidth(2)), + CountdownTimer( + endTime: DateTime.now() + .add(Duration(hours: 24)) + .millisecondsSinceEpoch, + widgetBuilder: (_, CurrentRemainingTime? time) { + if (time == null) { + return Text( + '00:00:00', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: + getProportionateScreenWidth(10), + color: baruTextutih, + ), + ); + } else { + return Text( + '${time.hours}:${time.min}:${time.sec}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: + getProportionateScreenWidth(10), + color: baruTextutih, + ), + ); + } + }, + ), + ], + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(4)), + Divider( + color: secondaryColor, + thickness: 1, + ), + Text( + "Kursus", + style: thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: orders.map((e) { + return listCourse( + imageUrl: e.imageUrl, + instructor: e.instructor, + title: e.title, + price: e.price, + discountPrice: e.discountPrice, + ); + }).toList(), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Text( + 'Metode Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "QRIS", + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + Container( + width: getProportionateScreenWidth(50), + height: getProportionateScreenWidth(17), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: + Theme.of(context).colorScheme.brightness == + Brightness.dark + ? Colors.transparent + : baruTexthitam.withOpacity(0.3), + spreadRadius: 1, + blurRadius: 3, + offset: Offset(0, 1), + ), + ], + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + fit: BoxFit.fill, + image: AssetImage("assets/images/qris.png"), + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(15)), + Text( + "Order ID", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Text( + detailOrder[0].idOrder, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData( + ClipboardData(text: detailOrder[0].idOrder)) + .then( + (_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Berhasil Menyalin Order ID'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(15)), + Text( + 'Total Pembayaran', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(6)), + Row( + children: [ + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(detailOrder[0].totalPayment))}', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: detailOrder[0].totalPayment)) + .then( + (_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Berhasil Menyalin Total Pembayaran'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler), + ), + ), + ], + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(13)), + PaymentInstructionGopay(), + SizedBox(height: getProportionateScreenHeight(17)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: GestureDetector( + child: Container( + padding: EdgeInsets.all(16), + width: double.infinity, + height: getProportionateScreenHeight(42), + decoration: BoxDecoration( + color: primaryColor, + border: Border.all( + color: primaryColor, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Lihat QRIS', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + fontWeight: semiBold, + color: baruTextutih, + ), + ), + ), + ), + onTap: () { + Navigator.pop(context); + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(21)), + ], + ), + ), + ), + ); + } + + Widget listCourse({ + String? imageUrl, + String? title, + String? instructor, + String? price, + String? discountPrice, + int? totalPrices, + }) { + return Container( + padding: EdgeInsets.symmetric(vertical: getProportionateScreenHeight(9)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(60), + height: getProportionateScreenHeight(30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + image: NetworkImage(imageUrl ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg'), + fit: BoxFit.cover, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/checkout/gopay/payment_instruction_gopay.dart b/lib/screens/checkout/gopay/payment_instruction_gopay.dart new file mode 100644 index 0000000..9d87337 --- /dev/null +++ b/lib/screens/checkout/gopay/payment_instruction_gopay.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:styled_text/styled_text.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class PaymentInstructionGopay extends StatelessWidget { + final TextStyle baris = thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(14), + ); + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(15), + horizontal: getProportionateScreenWidth(16), + ), + child: Text( + 'Cara Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + Container( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + StyledText( + text: + '1. Buka aplikasi <bold>Gojek</bold> atau <bold>e-Wallet</bold> apapun milik anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(15)), + StyledText( + text: + '2. Scan <bold>QR Code</bold> yang tertera dan masukkan nominal sesuai tagihan transaksi', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(15)), + StyledText( + text: + '3. Periksa detail transaksi Anda pada aplikasi, lalu tap tombol Bayar.', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(15)), + StyledText( + text: '4. Masukkan pin Anda', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(15)), + StyledText( + text: '5. Transaksi Anda telah selesai', + style: baris, + tags: { + 'bold': StyledTextTag(style: TextStyle(fontWeight: bold)), + }, + ), + SizedBox(height: getProportionateScreenHeight(15)), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/checkout/gopay/qr_code_gopay.dart b/lib/screens/checkout/gopay/qr_code_gopay.dart new file mode 100644 index 0000000..3598998 --- /dev/null +++ b/lib/screens/checkout/gopay/qr_code_gopay.dart @@ -0,0 +1,345 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/screens/checkout/batas_bayar.dart'; +import 'package:initial_folder/screens/checkout/gopay/batas_bayar_gopay.dart'; +import 'package:initial_folder/screens/checkout/gopay/gopay_payment_confirmation.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi_pending.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class QRCodeGopay extends StatelessWidget { + final List<String>? idCart; + QRCodeGopay({this.idCart}); + + @override + Widget build(BuildContext context) { + var detailOrder = + Provider.of<PaymentsProvider>(context, listen: false).detailOrder; + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + scrolledUnderElevation: 0.0, + leading: IconButton( + icon: Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + body: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10), + vertical: getProportionateScreenHeight(10), + ), + child: Column( + children: [ + Stack( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + "assets/images/qris_background.png", + width: getProportionateScreenWidth(328), + height: getProportionateScreenHeight(470), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(height: getProportionateScreenHeight(45)), + Text( + "VOCASIA", + style: secondaryTextStyle.copyWith( + fontWeight: bold, + fontSize: getProportionateScreenWidth(13), + color: baruTexthitam, + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(40)), + child: Text( + "Lakukan pembayaran dengan cara scan code dibawah ini dan lakukan pembayaran sesuai dengan tagihan yang diterima.", + textAlign: TextAlign.center, + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(9), + color: baruTexthitam, + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Center( + child: Container( + height: getProportionateScreenHeight(150), + width: getProportionateScreenWidth(150), + child: Image.network(detailOrder[0].qrCodeUrl! + // 'https://api.sandbox.midtrans.com/v2/gopay/916c417c-dd69-455f-9f8d-997b31d38c21/qr-code' + ), + ), + ), + // Image.network(), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + 'Order ID', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + color: baruTexthitam, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + Text( + detailOrder[0].idOrder, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + color: baruTexthitam, + fontWeight: semiBold, + letterSpacing: 1, + ), + ), + SizedBox(height: getProportionateScreenHeight(6)), + Text( + 'Total', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + color: baruTexthitam, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '').format(double.parse(detailOrder[0].totalPayment))}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + color: baruTexthitam, + fontWeight: semiBold, + letterSpacing: 2, + ), + ), + + // Center( + // child: Text( + // detailOrder[0].qrCodeUrl!.toString(), + // style: thirdTextStyle.copyWith( + // fontWeight: semiBold, + // fontSize: 14, + // color: Color(0xff181818)), + // ), + // ), + + // GestureDetector( + // child: Container( + // padding: EdgeInsets.all(16), + // width: double.infinity, + // height: getProportionateScreenHeight(44), + // decoration: BoxDecoration( + // color: Color(0xff25D366), + // border: Border.all(color: Color(0xff25D366), width: 1), + // borderRadius: BorderRadius.circular(10)), + // child: Center( + // child: Text( + // 'Pembayaran Gojek', + // style: thirdTextStyle.copyWith( + // fontSize: 14, color: Color(0xfff4f4f4)), + // ), + // ), + // ), + // onTap: () async { + // await openUrl(detailOrder[0].urlGopay!.toString()); + // }, + // ), + ], + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: GestureDetector( + child: Container( + padding: EdgeInsets.all(16), + width: double.infinity, + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Cara Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + fontWeight: semiBold, + ), + ), + Icon( + Icons.keyboard_arrow_down_outlined, + size: getProportionateScreenWidth(22), + color: Theme.of(context).colorScheme.onBackground, + ), + ], + ), + ), + onTap: () { + Navigator.push( + context, + CustomNavigator( + child: GopayPaymentConfirmation(), + ), + ); + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: GestureDetector( + child: Container( + padding: EdgeInsets.all(16), + width: double.infinity, + height: getProportionateScreenHeight(43), + decoration: BoxDecoration( + color: primaryColor, + border: Border.all( + color: primaryColor, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Unduh QRIS', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + fontWeight: semiBold, + color: baruTextutih, + ), + ), + ), + ), + onTap: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => GopayPaymentConfirmation())); + }, + ), + ), + SizedBox(height: 16), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: GestureDetector( + child: Container( + padding: EdgeInsets.all(16), + width: double.infinity, + height: getProportionateScreenHeight(43), + decoration: BoxDecoration( + border: Border.all( + color: primaryColor, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Cek Status Transaksi', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + color: primaryColor, + fontWeight: semiBold, + ), + ), + ), + ), + onTap: () { + print(idCart); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => BatasBayarGopay(idCart: idCart), + // ), + // ); + Navigator.pushReplacement( + context, + CustomNavigator( + child: RiwayatTransaksiPending(), + ), + ); + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + ], + ), + ), + ), + ); + } +} + +class GopaySplashScreen extends StatefulWidget { + final List<String>? idCart; + GopaySplashScreen({this.idCart}); + + @override + State<GopaySplashScreen> createState() => _GopaySplashScreenState(); +} + +class _GopaySplashScreenState extends State<GopaySplashScreen> { + @override + void initState() { + super.initState(); + Timer( + Duration(seconds: 2), + () => Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => QRCodeGopay(idCart: widget.idCart), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + // TODO: implement build + return Scaffold( + body: SafeArea( + child: Container( + color: Color(0xff4DC256), + alignment: Alignment.center, + child: Image( + image: AssetImage('assets/images/gopay1.png'), + height: 50, + ), + )), + ); + } +} + +Future<void> openUrl(String url, + {bool forceWebView = false, enableJavaScript = false}) async { + await launchUrl(Uri.parse(url)); +} diff --git a/lib/screens/checkout/not_success_paid_course.dart b/lib/screens/checkout/not_success_paid_course.dart new file mode 100644 index 0000000..a1cf13d --- /dev/null +++ b/lib/screens/checkout/not_success_paid_course.dart @@ -0,0 +1,151 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/providers/payments_provider.dart'; +import 'package:initial_folder/screens/home/components/body_comp/latest_course.dart'; +import 'package:initial_folder/screens/home/components/home_page.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +// import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:provider/provider.dart'; + +class NotSuccessPaidCourse extends StatelessWidget { + const NotSuccessPaidCourse({ + Key? key, + this.title, + this.id, + this.thumbnail, + this.instructor, + }) : super(key: key); + + final String? instructor, id, thumbnail, title; + @override + Widget build(BuildContext context) { + PageProvider pageProvider = Provider.of<PageProvider>(context); + + var orders = Provider.of<OrderProvider>(context).orders; + return Scaffold( + appBar: AppBar( + leadingWidth: 14, + ), + body: ListView( + children: [ + SizedBox(height: getProportionateScreenHeight(8)), + Text( + 'TRANSAKSI DIBATALKAN', + style: secondaryTextStyle.copyWith( + fontWeight: bold, + color: primaryColor, + fontSize: getProportionateScreenWidth(20), + letterSpacing: 1), + textAlign: TextAlign.center, + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + // Column( + // children: [ + // Text( + // 'Order ID', + // style: primaryTextStyle.copyWith( + // letterSpacing: 0.5, + // fontWeight: bold, + // fontSize: getProportionateScreenWidth(10), + // color: secondaryColor, + // ), + // ), + // Text( + // '123', + // style: primaryTextStyle.copyWith( + // letterSpacing: 0.5, + // fontWeight: reguler, + // fontSize: getProportionateScreenWidth(12), + // color: tenthColor, + // ), + // ), + // ], + // ), + // SizedBox( + // height: getProportionateScreenHeight(16), + // ), + Container( + margin: EdgeInsets.only(left: 16, right: 16), + width: SizeConfig.screenWidth, + height: getProportionateScreenHeight(180), + child: Column( + children: [ + Container( + width: getProportionateScreenWidth(278), + height: getProportionateScreenHeight(156), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + image: NetworkImage(thumbnail ?? + // '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg' + orders[0].imageUrl), + fit: BoxFit.fill)), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + bottom: getProportionateScreenHeight(8), + ), + child: Center( + child: Text( + title ?? '', + style: secondaryTextStyle.copyWith( + fontSize: 20, letterSpacing: 0.2), + textAlign: TextAlign.center, + ), + ), + ), + Container( + child: Center( + child: Text( + instructor == null + ? 'Oleh ${orders[0].instructor}' + : 'Oleh $instructor ', + style: + primaryTextStyle.copyWith(fontSize: 14, letterSpacing: 0.5), + textAlign: TextAlign.center, + ), + ), + ), + Container( + child: Text( + orders.length == 1 ? '' : '(${orders.length} kursus lainnya)'), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + Container( + margin: EdgeInsets.only(left: 16, right: 16), + child: DefaultButton( + text: 'Kembali Ke Home', + press: () async { + await Provider.of<MyCourseProvider>(context, listen: false) + .getMyCourse(); + pageProvider.currentIndex = 0; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => HomePage()), + (route) => false); + }, + ), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + LatestCourse(text: "Kursus Terbaru") + ], + ), + ); + } +} diff --git a/lib/screens/checkout/snap_payment_page.dart b/lib/screens/checkout/snap_payment_page.dart new file mode 100644 index 0000000..e579b44 --- /dev/null +++ b/lib/screens/checkout/snap_payment_page.dart @@ -0,0 +1,361 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:file_saver/file_saver.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi_pending.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:url_launcher/url_launcher.dart'; // Import url_launcher + +import 'success_paid_course.dart'; + +class SnapPaymentPage extends StatefulWidget { + final String transactionToken; + final String orderId; + final int grossAmount; + + final String courseTitle; + final String courseThumbnail; + final String courseInstructor; + final String courseId; + + SnapPaymentPage({ + required this.transactionToken, + required this.orderId, + required this.grossAmount, + required this.courseTitle, + required this.courseThumbnail, + required this.courseInstructor, + required this.courseId, + }); + + @override + _SnapPaymentPageState createState() => _SnapPaymentPageState(); +} + +class _SnapPaymentPageState extends State<SnapPaymentPage> { + // Controller untuk WebView + late WebViewController _controller; + double _progress = 0; // Menyimpan progress loading WebView + bool _isLoading = true; // Status loading halaman + String? baseUrlmidtrans = dotenv.env['BASE_URL_MIDTRANS']; // Base URL Midtrans + + @override + void initState() { + super.initState(); + // Inisialisasi WebViewController dan pengaturan event handler + _controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: _onProgress, // Handler progress loading + onPageStarted: _onPageStarted, // Handler saat halaman mulai dimuat + onPageFinished: _onPageFinished, // Handler saat halaman selesai dimuat + onWebResourceError: _onWebResourceError, // Handler error resource + onNavigationRequest: _onNavigationRequest, // Handler navigasi + ), + ) + ..addJavaScriptChannel( + 'BlobDataChannel', + // Handler pesan dari JavaScript untuk download QRIS + onMessageReceived: (JavaScriptMessage message) async { + if (message.message.startsWith('error:')) { + // Jika error saat fetch blob + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Download Sedang Tidak Tersedia'), + content: Text( + 'Silahkan screenshot Qris tersebut untuk menyimpan ke perangkat kamu.'), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('OK'), + ), + ], + ), + ); + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar(content: Text('Gagal mengunduh QRIS. Silakan coba lagi.')), + // ); + } else { + // Jika berhasil, proses data blob + await _handleBlobData(message.message); + } + }, + ) + // Memuat halaman pembayaran Midtrans + ..loadRequest(Uri.parse( + "$baseUrlmidtrans/snap/v2/vtweb/${widget.transactionToken}", + )); + } + + // Handler progress loading WebView + void _onProgress(int progressValue) { + setState(() { + _progress = progressValue / 100; + }); + } + + // Handler saat halaman mulai dimuat + void _onPageStarted(String url) { + setState(() { + _isLoading = true; + }); + } + + // Handler saat halaman selesai dimuat + void _onPageFinished(String url) { + setState(() { + _isLoading = false; + _progress = 0; + }); + } + + // Handler jika terjadi error pada resource WebView + void _onWebResourceError(WebResourceError error) { + setState(() { + _isLoading = false; + }); + _showErrorDialog(error.description ?? 'Unknown Error'); + } + + // Handler navigasi pada WebView + NavigationDecision _onNavigationRequest(NavigationRequest request) { + // Redirect ke aplikasi pembayaran jika diperlukan + if (request.url.startsWith('https://gopay.co.id/') || + request.url.startsWith('https://app.shopeepay.co.id/')) { + print('Redirecting to GoPay/GoJek app'); + _launchURL(request.url); + return NavigationDecision.prevent; + } + + // Jika link blob (download QRIS), jalankan fetch blob + if (request.url.startsWith('blob:')) { + print('Redirecting download qris'); + + _fetchBlobData(baseUrlmidtrans ?? "https://app.sandbox.midtrans.com", + widget.transactionToken); + return NavigationDecision.prevent; + } + + // Redirect ke halaman riwayat transaksi jika status pending + if (request.url.contains('transaction_status=pending') || + request.url.contains('https://vocasia.id/')) { + Navigator.of(context) + .pushReplacementNamed(RiwayatTransaksiPending.routeName); + return NavigationDecision.prevent; + } + + // Jika pembayaran sukses, tambahkan order dan redirect ke halaman sukses + if (request.url.contains('transaction_status=settlement') || + request.url.contains('transaction_status=capture')) { + var orderProvider = Provider.of<OrderProvider>(context, listen: false); + orderProvider.addOrder( + id: widget.courseId, + title: widget.courseTitle, + price: widget.grossAmount.toString(), + imageUrl: widget.courseThumbnail, + instructor: widget.courseInstructor, + discountPrice: '', + ); + + Navigator.of(context).pushReplacementNamed( + SuccessPaidCourse.routeName, + ); + return NavigationDecision.prevent; + } + + // Jika user menekan tombol kembali di halaman pembayaran + if (request.url.contains('action=back')) { + Navigator.of(context).pop(); + return NavigationDecision.prevent; + } + + // Default: lanjutkan navigasi + return NavigationDecision.navigate; + } + + // Menjalankan JavaScript untuk mengambil data blob QRIS dari WebView + Future<void> _fetchBlobData(String baseUrl, String transactionId) async { + print('Fetching transaction URL: $transactionId'); + final realUrl = + baseUrl + "/snap/v1/transactions/" + transactionId + "/qr-code"; + final script = ''' + (async function() { + try { + const response = await fetch('$baseUrl') + const blob = await response.blob(); + const reader = new FileReader(); + reader.onloadend = function() { + const base64data = reader.result.split(',')[1]; + BlobDataChannel.postMessage(base64data); + }; + reader.readAsDataURL(blob); + } catch (error) { + console.error('Failed to fetch blob:', error); + BlobDataChannel.postMessage('error: ' + error.message); + } + })(); + '''; + _controller.runJavaScript(script); + } + + // Menyimpan data QRIS hasil download ke file lokal + Future<void> _handleBlobData(String base64Data) async { + try { + final decodedBytes = base64Decode(base64Data); + final directory = await getApplicationDocumentsDirectory(); + final path = directory.path; + final file = File('$path/qris_download.png'); + await file.writeAsBytes(decodedBytes); + + await FileSaver.instance.saveAs( + name: 'qris_download', + ext: 'png', + mimeType: MimeType.png, + file: file, + ); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('QRIS berhasil diunduh')), + ); + } catch (e) { + _showErrorDialog('Gagal mengunduh QRIS: $e'); + } + } + + // Menampilkan dialog error + void _showErrorDialog(String errorMessage) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Error'), + content: Text(errorMessage), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text('OK'), + ), + ], + ), + ); + } + + // Membuka aplikasi eksternal (misal: GoPay/ShopeePay) + Future<void> _launchURL(String url) async { + if (await canLaunch(url)) { + await launch(url); + } else { + _showErrorDialog('Could not open the app'); + } + } + + // Handler ketika user menekan tombol back (hardware/software) + Future<bool> _onWillPop() async { + return (await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('Pembayaran Belum Selesai'), + content: Text('Apakah Anda yakin ingin keluar dari halaman ini?'), + actions: <Widget>[ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text('Batal'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pop(true); + Navigator.of(context) + .pushReplacementNamed(RiwayatTransaksi.routeName); + }, + child: Text('Keluar'), + ), + ], + ), + )) ?? + false; + } + + // Melakukan reload halaman pembayaran + void _refreshPage() { + _controller.reload(); + } + + @override + Widget build(BuildContext context) { + // Widget utama halaman pembayaran + return WillPopScope( + onWillPop: _onWillPop, + child: Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(kToolbarHeight), + child: AppBar( + elevation: 5, + title: Center( + child: Text( + 'Pembayaran', + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + ), + backgroundColor: primaryColor, + iconTheme: IconThemeData(color: Colors.white), + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () async { + bool shouldExit = await _onWillPop(); + if (shouldExit) { + Navigator.of(context) + .pushReplacementNamed(RiwayatTransaksi.routeName); + } + }, + ), + actions: [ + IconButton( + icon: Icon(Icons.refresh), + onPressed: _refreshPage, + ), + ], + ), + ), + body: Stack( + children: [ + // Widget WebView untuk menampilkan halaman pembayaran + RefreshIndicator( + onRefresh: () async { + _refreshPage(); + }, + child: WebViewWidget(controller: _controller), + ), + // Progress bar saat loading + if (_isLoading) + LinearProgressIndicator( + value: _progress, + color: sevenColor, + backgroundColor: secondaryColor, + ), + // Spinner loading di tengah layar + if (_isLoading) + Center( + child: CircularProgressIndicator(), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/checkout/success_paid_course.dart b/lib/screens/checkout/success_paid_course.dart new file mode 100644 index 0000000..67b2807 --- /dev/null +++ b/lib/screens/checkout/success_paid_course.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button_payment.dart'; +import 'package:provider/provider.dart'; + +import '../../providers/my_course_provider.dart'; + +class SuccessPaidCourse extends StatefulWidget { + static const String routeName = "/success-paid-course"; + + @override + State<SuccessPaidCourse> createState() => _SuccessPaidCourseState(); +} + +class _SuccessPaidCourseState extends State<SuccessPaidCourse> { + @override + Widget build(BuildContext context) { + var orders = Provider.of<OrderProvider>(context).orders; + var pageProvider = Provider.of<PageProvider>(context); + + return Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + leadingWidth: 14, + ), + body: ListView( + children: [ + Image.asset( + "assets/images/success_pay.png", + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(100), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Text( + 'Pembayaran Berhasil', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + textAlign: TextAlign.center, + ), + SizedBox(height: getProportionateScreenHeight(15)), + Container( + margin: EdgeInsets.symmetric(horizontal: 16), + width: SizeConfig.screenWidth, + height: getProportionateScreenHeight(169), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + ), + child: Column( + children: [ + Container( + width: getProportionateScreenWidth(278), + height: getProportionateScreenHeight(145), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + image: NetworkImage( + orders[0].imageUrl, // Ambil dari OrderProvider + ), + fit: BoxFit.cover, + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(8), + ), + child: Center( + child: Text( + orders[0].title ?? '', // Ambil dari OrderProvider + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.2, + ), + textAlign: TextAlign.center, + ), + ), + ), + ), + Container( + child: Center( + child: Text( + 'Oleh ${orders[0].instructor}', // Ambil dari OrderProvider + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + letterSpacing: 0.5, + fontFamily: "Poppins", + ), + textAlign: TextAlign.center, + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Container( + margin: EdgeInsets.symmetric(horizontal: 16), + child: DefaultButtonPayment( + width: double.infinity, + height: getProportionateScreenHeight(34), + text: 'Lihat Kursus', + press: () async { + await Provider.of<MyCourseProvider>(context, listen: false) + .getMyCourse(); + pageProvider.currentIndex = 2; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => HomeScreen(), + ), + (route) => false, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/coupon/coupon_page.dart b/lib/screens/coupon/coupon_page.dart new file mode 100644 index 0000000..023931d --- /dev/null +++ b/lib/screens/coupon/coupon_page.dart @@ -0,0 +1,205 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/screens/coupon/success_radem_coupon_page.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator_bottom.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; + +class CouponPage extends StatefulWidget { + CouponPage({Key? key}) : super(key: key); + + @override + State<CouponPage> createState() => _CouponPageState(); +} + +class _CouponPageState extends State<CouponPage> { + final kuponController = TextEditingController(); + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + bool isLoading = false; + bool failed = false; + String _iconPath = 'assets/icons/not_checklist.svg'; + String? errorMessage; + + handleRadeem() { + setState(() { + isLoading = true; + }); + + if (kuponController.text.isEmpty) { + setState(() { + isLoading = false; + errorMessage = 'Kupon tidak boleh kosong'; + }); + } else if (kuponController.text.length <= 3) { + setState(() { + isLoading = false; + errorMessage = 'Kupon tidak valid'; + }); + } else { + setState(() { + errorMessage = null; + }); + Navigator.pushReplacement( + context, + CustomNavigatorBottom( + child: SuccessRademCouponPage( + coupon: kuponController.text, + ), + ), + ); + setState(() { + _iconPath = 'assets/icons/not_checklist.svg'; + isLoading = false; + }); + } + } + + void _validateInputs() { + if (this._formKey.currentState!.validate()) { + handleRadeem(); + } + } + + @override + Widget build(BuildContext context) { + return Container( + height: MediaQuery.of(context).viewInsets.bottom > 0 + ? MediaQuery.of(context).size.height / 1.6 + : MediaQuery.of(context).size.height / 2.4, + child: SingleChildScrollView( + child: Center( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(5), + top: getProportionateScreenHeight(5), + ), + child: IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Icon(Icons.close), + ), + ), + ], + ), + Text( + 'Tukarkan Voucher', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12)), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Text( + 'Masukkan kode voucher untuk klaim promo menarik dari\nVocasia', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10)), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(30)), + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : secondaryColor.withOpacity(0.3), + ), + height: 40, + child: Form( + key: _formKey, + child: TextFormField( + autofocus: false, + controller: kuponController, + onChanged: (value) { + setState(() { + if (value.isEmpty) { + _iconPath = 'assets/images/not_checklist.svg'; + } else if (value.length > 2) { + _iconPath = 'assets/icons/checklist.svg'; + } else { + _iconPath = 'assets/icons/wrong.svg'; + } + }); + }, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ), + cursorColor: secondaryColor, + decoration: InputDecoration( + border: InputBorder.none, + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: sevenColor), + borderRadius: BorderRadius.circular(10)), + contentPadding: EdgeInsets.only( + left: getProportionateScreenWidth(20), + bottom: getProportionateScreenHeight(8), + top: getProportionateScreenHeight(2), + ), + hintText: 'Masukkan Kode Voucher', + hintStyle: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: secondaryColor, + letterSpacing: 0.5, + ), + suffixIcon: Transform.scale( + scale: 0.5, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + child: SvgPicture.asset(_iconPath), + ), + ), + ), + ), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + if (errorMessage != null) + Text( + errorMessage!, + style: TextStyle(color: Colors.red), + ), + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + top: getProportionateScreenHeight(10), + ), + child: isLoading + ? LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: Colors.white, + ) + : TapDebouncer( + cooldown: const Duration(milliseconds: 3000), + onTap: () async => await {_validateInputs()}, + builder: + (BuildContext context, TapDebouncerFunc? onTap) { + return DefaultButton( + text: 'Tukarkan', + press: onTap, + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/coupon/success_radem_coupon_page.dart b/lib/screens/coupon/success_radem_coupon_page.dart new file mode 100644 index 0000000..40a5ed5 --- /dev/null +++ b/lib/screens/coupon/success_radem_coupon_page.dart @@ -0,0 +1,369 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/models/discount_course_model.dart'; +import 'package:initial_folder/providers/search_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_voucher_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card_coupon.dart'; +import 'package:initial_folder/screens/search_course/component/filter.dart'; +import 'package:initial_folder/services/coupon_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/search_not_found.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/filters_course_provider.dart' + as filterCourseProvider; +import 'package:initial_folder/providers/coupon_course_provider.dart' + as couponProv; + +class SuccessRademCouponPage extends StatefulWidget { + const SuccessRademCouponPage({ + required this.coupon, + Key? key, + }) : super(key: key); + + final coupon; + + @override + State<SuccessRademCouponPage> createState() => _SuccessRademCouponPageState(); +} + +class _SuccessRademCouponPageState extends State<SuccessRademCouponPage> { + bool _hasShownErrorToast = false; + + @override + Widget build(BuildContext context) { + final selected = Provider.of<TotalPriceProvider>(context); + return Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + ), + body: SingleChildScrollView( + child: ChangeNotifierProvider( + create: (_) => couponProv.CouponCourseProvider( + couponService: CouponService(), coupon: widget.coupon), + child: Consumer<couponProv.CouponCourseProvider>( + builder: (context, state, _) { + if (state.state == couponProv.ResultState.Loading) { + return Container( + height: MediaQuery.of(context).size.height, + width: MediaQuery.of(context).size.width, + child: Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ), + ); + } else if (state.state == couponProv.ResultState.HasData) { + _hasShownErrorToast = false; + if (state.result is List) { + List<DiscountCourseModel> discountCourse = state.result; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: getProportionateScreenHeight(110), + width: MediaQuery.of(context).size.width, + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(8), + vertical: getProportionateScreenHeight(16)), + decoration: BoxDecoration( + color: Theme.of(context).brightness == + Brightness.dark + ? fifteenColor + : baruTextutih, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 4), + ), + ], + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Voucher Berhasil DItukar', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: + getProportionateScreenWidth(12)), + ), + Text( + 'Ayo pilih kursus yang sesuai dengan keinginan kamu', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10)), + ), + ], + ), + ), + // SizedBox(height: getProportionateScreenHeight(15)), + // PreferredSize( + // preferredSize: Size.fromHeight( + // getProportionateScreenWidth(57)), + // child: AppBar( + // automaticallyImplyLeading: false, + // scrolledUnderElevation: 0, + // backgroundColor: + // Theme.of(context).colorScheme.background, + // leadingWidth: 30, + // actions: [ + // IconButton( + // padding: EdgeInsets.zero, + // onPressed: () => Navigator.of(context, + // rootNavigator: true) + // .push( + // CustomNavigator( + // child: const Filter(), + // ), + // ), + // icon: Icon( + // Icons.tune_rounded, + // color: primaryColor, + // ), + // ), + // ], + // title: Container( + // decoration: BoxDecoration( + // borderRadius: BorderRadius.circular(10), + // color: Theme.of(context).brightness == + // Brightness.dark + // ? seventeenColor + // : secondaryColor.withOpacity(0.3), + // ), + // height: 40, + // child: Consumer<SearchProvider>( + // builder: (context, state, _) => TextField( + // autofocus: false, + // onSubmitted: (value) { + // Provider.of< + // filterCourseProvider + // .FilterCourseProvider>( + // context, + // listen: false) + // .isSearchsFalse(); + // filterCourseProvider + // .FilterCourseProvider + // filterCourseProv = Provider.of< + // filterCourseProvider + // .FilterCourseProvider>( + // context, + // listen: false); + // state.searchText = + // validatorSearch(value); + // state.initSearchCourse( + // price: filterCourseProv + // .currentIndexPrice, + // level: + // filterCourseProv.levels.join(','), + // rating: filterCourseProv + // .currentIndexRating, + // ); + // }, + // style: primaryTextStyle.copyWith( + // fontSize: + // getProportionateScreenWidth(14), + // letterSpacing: 0.5, + // ), + // onChanged: (value) { + // state.searchText = + // validatorSearch(value); + // }, + // cursorColor: secondaryColor, + // decoration: InputDecoration( + // border: InputBorder.none, + // errorBorder: OutlineInputBorder( + // borderSide: + // BorderSide(color: sevenColor), + // borderRadius: + // BorderRadius.circular(10)), + // contentPadding: EdgeInsets.only( + // top: getProportionateScreenHeight( + // 2)), + // prefixIcon: Icon( + // FeatherIcons.search, + // size: 20, + // color: primaryColor, + // ), + // hintText: 'Cari Kursus', + // hintStyle: primaryTextStyle.copyWith( + // fontSize: + // getProportionateScreenWidth(12), + // color: secondaryColor, + // letterSpacing: 0.5, + // ), + // ), + // ), + // ), + // ), + // ), + // ), + SizedBox(height: getProportionateScreenHeight(24)), + Text( + 'Kursus Terkait', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(16)), + GridView.builder( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(20), + left: getProportionateScreenWidth(5), + bottom: getProportionateScreenHeight(13), + ), + physics: ScrollPhysics(), + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: discountCourse[0].typeCoupon == "1" + ? 3.1 / 4 + : 2.8 / 4, + crossAxisSpacing: 16, + mainAxisSpacing: 14, + ), + itemCount: discountCourse.length, + itemBuilder: (context, index) { + var course = discountCourse[index]; + var finalPriceReal = + course.finalPrice.toString().replaceAll(".", ""); + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(12)), + child: ProductCardCoupon( + totalDiscount: int.parse(course.value!) > 99 || + course.typeCoupon == "1" + ? 0 + : int.parse(course.value!), + students: course.students ?? '0', + id: course.idCourse, + thumbnail: course.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: course.title, + instructorName: course.instructorName, + specificRating: (course.rating.isNotEmpty && + course.rating[0]?.avgRating != null) + ? course.rating[0]!.avgRating.toString() + : '0', + rating: (course.rating.isNotEmpty && + course.rating[0]?.avgRating != null) + ? course.rating[0]!.avgRating.toString() + : '5.0', + numberOfRatings: (course.rating.isNotEmpty && + course.rating[0]?.totalReview != null) + ? course.rating[0]!.totalReview! + : '0', + isTopCourse: course.topCourse ?? '', + price: (course.finalPrice.toString() == '0') + ? 'Gratis' + : numberFormat(course.finalPrice.toString()), + realPrice: (course.price == '0') + ? '' + : course.typeCoupon != "1" + ? numberFormat(course.price) + : "", + press: () { + selected.selectedPriceCoupon = + int.parse(course.price); + selected.selectedTypeCoupon = + course.typeCoupon.toString(); + selected.selectedCouponText = widget.coupon; + selected.selectedFinalPriceCoupon = + int.parse(finalPriceReal); + selected.selectedTotalPrice = + int.parse(finalPriceReal); + selected.selectedPotonganKupon = int.parse( + course.hargaTotalDiscount.toString()); + selected.selectedValuePrice = + course.value.toString(); + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: DetailVoucherScreen( + idcourse: course.idCourse, + coupon: widget.coupon, + ), + ), + ); + }, + ), + ); + }, + ) + ], + ); + } else if (state.result is String) { + String message = state.result; + + return Center( + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(15), + right: getProportionateScreenWidth(15), + top: getProportionateScreenHeight(140), + ), + child: Text( + textAlign: TextAlign.center, + message, + style: thirdTextStyle, + ), + ), + ); + } else { + return Center(child: Text('')); + } + } else if (state.state == couponProv.ResultState.NoData) { + _hasShownErrorToast = false; + return SearchNotFound(); + } else if (state.state == couponProv.ResultState.Error) { + if (!_hasShownErrorToast) { + _hasShownErrorToast = true; + Future.delayed(Duration.zero, () { + Navigator.pop(context); + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kupon Tidak Valid", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context); + }); + } + return SearchNotFound(); + } else { + return Center(child: Text('')); + } + }, + ), + ), + ), + ); + } +} diff --git a/lib/screens/course/component/announcement.dart b/lib/screens/course/component/announcement.dart new file mode 100644 index 0000000..b26ec43 --- /dev/null +++ b/lib/screens/course/component/announcement.dart @@ -0,0 +1,134 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/section_model.dart'; +import 'package:initial_folder/providers/selected_title_provider.dart'; +import 'package:initial_folder/services/announcement_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/announcement_user_page.dart'; +import 'package:provider/provider.dart'; + +class Announcement extends StatefulWidget { + const Announcement({ + Key? key, + required this.id, + required this.lesonOper, + required this.sectionOper, + required this.lessonMapIdoper, + required this.dataLessonOper, + }) : super(key: key); + + final id; + final lesonOper; + final sectionOper; + final lessonMapIdoper; + final List<DataLesson> dataLessonOper; + + @override + State<Announcement> createState() => _AnnouncementState(); + + String? showSummary(String? selectedTitle) { + String? selectedSummary = dataLessonOper + .firstWhere((data) => data.title == selectedTitle, + orElse: () => DataLesson( + summary: ' Tidak ada ringkasan')) + .summary; + + return selectedSummary; + } +} + +class _AnnouncementState extends State<Announcement> { + double value = 0; + final _controller = TextEditingController(); + + @override + void initState() { + super.initState(); + AnnouncementService().getAnnouncement(widget.id); + } + + @override + Widget build(BuildContext context) { + final selectedTitle = + Provider.of<SelectedTitleProvider>(context).selectedTitle; + + return SingleChildScrollView( + child: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + top: getProportionateScreenWidth(16), + ), + child: Text( + 'Ringkasan Kursus', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + top: getProportionateScreenWidth(10), + ), + child: Column( + children: [ + if (widget.showSummary(selectedTitle) != "") + Text( + widget.showSummary(selectedTitle)!, + style: thirdTextStyle, + ) + else + Center( + child: Text( + 'Tidak Ada Ringkasan', + style: thirdTextStyle, + ), + ), + ], + ), + ), + ], + ), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + top: getProportionateScreenWidth(16), + ), + child: Text( + 'Pengumuman', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(14)), + AnnouncementUserPage(idCourse: widget.id), + ], + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + + super.dispose(); + } +} diff --git a/lib/screens/course/component/detail_play_course.dart b/lib/screens/course/component/detail_play_course.dart new file mode 100644 index 0000000..ec780c3 --- /dev/null +++ b/lib/screens/course/component/detail_play_course.dart @@ -0,0 +1,710 @@ +import 'dart:convert'; + +import 'package:expandable/expandable.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/section_lesson_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/detail_course/components/instruktur.dart'; +import 'package:initial_folder/screens/detail_course/components/kursus_include_item.dart'; +import 'package:provider/provider.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class DetailPlayCourse extends StatefulWidget { + const DetailPlayCourse({super.key}); + + @override + State<DetailPlayCourse> createState() => _DetailPlayCourseState(); +} + +class _DetailPlayCourseState extends State<DetailPlayCourse> { + bool isExpanded = false; + bool isExpanded2 = false; + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + SectionLessonProvider sectionLessonProvider = + Provider.of<SectionLessonProvider>(context); + final themeProvider = Provider.of<ThemeProvider>(context); + + Widget kemampuanDiraih(String title) { + return Container( + margin: EdgeInsets.only(bottom: 10), + child: Row( + children: [ + Icon(Icons.check, + size: getProportionateScreenHeight(13), color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode,), + SizedBox( + width: 10, + ), + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Text( + title, + style: thirdTextStyle.copyWith( + fontWeight: light, + color: Theme.of(context).colorScheme.onBackground, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ], + ), + ); + } + + Widget persyaratan(String title) { + return Container( + margin: EdgeInsets.only(bottom: 10), + child: Row( + children: [ + title.isNotEmpty + ? Icon( + Icons.brightness_1, + size: getProportionateScreenHeight(13), + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ) + : SizedBox.shrink(), + SizedBox( + width: 10, + ), + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Text( + title, + style: thirdTextStyle.copyWith( + fontWeight: light, + color: Theme.of(context).colorScheme.onBackground, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ], + ), + ); + } + + return Consumer<DetailCourseProvider>(builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + var detailCourse = state.result!.data[0][0]; + print(detailCourse.outcome); + print(detailCourse.requirement); + + List requirement; + List outcomes = []; + + if (detailCourse.outcome != '') { + outcomes = jsonDecode(detailCourse.outcome ?? 'gagal'); + } + + try { + requirement = jsonDecode(detailCourse.requirement ?? '[]'); + } catch (e) { + requirement = []; + } + + final bool hasDescription = detailCourse.description!.isNotEmpty; + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(9)), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + top: getProportionateScreenHeight(10), + ), + child: Text( + 'Kursus Ini Sudah Termasuk', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + SizedBox(height: 10), + KursusIncludeItems( + svg: 'assets/icons/clock.svg', + text: + '${detailCourse.totalDuration} video pembelajaran'), + KursusIncludeItems( + svg: 'assets/icons/lesson.svg', + text: '${detailCourse.totalLesson} pelajaran'), + KursusIncludeItems( + svg: 'assets/icons/calendar.svg', + text: 'Akses full seumur hidup'), + KursusIncludeItems( + svg: 'assets/icons/phone.svg', + text: ' Akses di ponsel dan TV '), + ], + ), + ), + SizedBox( + height: 5, + ), + ExpandableNotifier( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 12, + ), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(106), + bottom: getProportionateScreenWidth(8), + ), + child: Text('Kemampuan Yang Akan Diraih', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14))), + ), + Expandable( + collapsed: ExpandableButton( + child: Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + outcomes.isEmpty + ? Row( + children: [ + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Text( + '', + ), + ), + ), + ], + ) + : Column(children: [ + ...outcomes + .map((e) => kemampuanDiraih(e)) + .take(2) + ]), + if (outcomes.isNotEmpty && outcomes.length >= 3) + SizedBox(height: 8), + if (outcomes.isNotEmpty && outcomes.length >= 3) + Container( + child: Text( + "Tampilkan Lebih Banyak", + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: + getProportionateScreenWidth(12), + ), + ), + ), + ], + ), + ), + ), + expanded: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ExpandableButton( + child: Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + outcomes.isEmpty + ? Row( + children: [ + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Text( + '', + ), + ), + ), + ], + ) + : Column( + children: outcomes + .map((e) => kemampuanDiraih(e)) + .toList(), + ), + if (outcomes.isNotEmpty && + outcomes.length >= 3) + SizedBox( + height: 16, + ), + if (outcomes.isNotEmpty && + outcomes.length >= 3) + Container( + child: Text( + "Tampilkan Lebih Sedikit", + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + color: + Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: + getProportionateScreenWidth(12), + ), + ), + ) + ], + ), + ), + ) + ], + ), + ), + ], + ), + ), + ExpandableNotifier( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 12, + ), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(106), + bottom: getProportionateScreenWidth(8), + ), + child: Text('Persyaratan', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14))), + ), + Expandable( + collapsed: ExpandableButton( + child: Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + requirement.isEmpty + ? Row( + children: [ + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Text( + '', + ), + ), + ), + ], + ) + : Column(children: [ + ...requirement + .map((e) => persyaratan(e)) + .take(2) + ]), + if (requirement.isNotEmpty && + requirement.length >= 3) + SizedBox(height: 8), + if (requirement.isNotEmpty && + requirement.length >= 3) + Container( + child: Text( + "Tampilkan Lebih Banyak", + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: + getProportionateScreenWidth(12), + ), + ), + ), + ], + ), + ), + ), + expanded: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ExpandableButton( + child: Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + requirement.isEmpty + ? Row( + children: [ + Expanded( + child: Align( + alignment: Alignment.topLeft, + child: Text( + '', + ), + ), + ), + ], + ) + : Column( + children: requirement + .map((e) => persyaratan(e)) + .toList(), + ), + if (requirement.isNotEmpty && + requirement.length >= 3) + SizedBox( + height: 16, + ), + if (requirement.isNotEmpty && + requirement.length >= 3) + Container( + child: Text( + "Tampilkan Lebih Sedikit", + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + color: + Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: + getProportionateScreenWidth(12), + ), + ), + ) + ], + ), + ), + ) + ], + ), + ), + ], + ), + ), + + SizedBox(height: getProportionateScreenHeight(5)), + SizedBox( + height: 14, + ), + Align( + alignment: Alignment.topLeft, + child: Container( + margin: + EdgeInsets.only(left: getProportionateScreenWidth(10)), + child: Text( + 'Deskripsi', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + ), + AnimatedSize( + curve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 300), + child: detailCourse.description!.isNotEmpty + ? Container( + height: sectionLessonProvider.isExpanded ? 55 : null, + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(5)), + child: Html( + data: detailCourse.description! + .split('\n') + .take(2) + .join('\n'), + style: { + "body": Style( + fontSize: + FontSize(getProportionateScreenWidth(12)), + fontWeight: FontWeight.bold, + fontFamily: 'Poppins', + textAlign: TextAlign.justify, + color: Theme.of(context).brightness == + Brightness.dark + ? Colors.grey[100] + : Colors.grey[600], + ), + }, + ), + ) + : SizedBox(), + ), + Padding( + padding: + EdgeInsets.only(left: getProportionateScreenWidth(3)), + child: detailCourse.description!.length > 140 + ? TextButton( + style: TextButton.styleFrom( + foregroundColor: primaryColor), + onPressed: () => sectionLessonProvider.expanded(), + child: sectionLessonProvider.isExpanded + ? Text( + 'Tampilkan Lebih Banyak', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: getProportionateScreenWidth(12), + ), + textAlign: TextAlign.left, + ) + : Text( + 'Tampilkan Lebih Sedikit', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: getProportionateScreenWidth(12), + ), + ), + ) + : SizedBox(), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (hasDescription && + detailCourse.description!.length > 120) + SizedBox(height: getProportionateScreenHeight(10)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Row( + children: [ + CircleAvatar( + radius: 20, + backgroundColor: primaryColor, + backgroundImage: detailCourse.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage(detailCourse.fotoProfile!) + as ImageProvider, + ), + SizedBox(width: getProportionateScreenWidth(10)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + detailCourse.instructor ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: bold, + ), + ), + Row( + children: [ + Text( + 'Instructor, ', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ), + Text( + '${detailCourse.totalStudents ?? '0'} Murid, ', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ), + Text( + '${detailCourse.totalLesson ?? ''} Kursus', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ) + ], + ), + ], + ), + ], + ), + ), + // SizedBox(height: 2), + // widget.headline != null + // ? Container( + // margin: EdgeInsets.only(left: 10), + // child: Text( + // widget.headline!, + // style: TextStyle(fontSize: 13), + // ), + // ) + // : const SizedBox.shrink(), + // Container( + // margin: EdgeInsets.only(left: 10), + // child: Row( + // children: [ + // RatingBarIndicator( + // itemSize: 11, + // rating: double.parse(widget.rating ?? '0'), + // direction: Axis.horizontal, + // itemCount: 5, + // itemBuilder: (context, _) => const FaIcon( + // FontAwesomeIcons.solidStar, + // color: Colors.amber, + // ), + // ), + // SizedBox(width: 4), + // Text( + // double.parse(widget.rating ?? '0').toString(), + // style: TextStyle(fontSize: 10), + // ), + // SizedBox(width: 4), + // Text( + // '(${widget.review ?? '0'})', + // style: TextStyle(fontSize: 10), + // ), + // ], + // ), + // ), + if (detailCourse.bio == null || detailCourse.bio!.isEmpty) + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenHeight(10), + right: getProportionateScreenHeight(10), + bottom: getProportionateScreenHeight(10), + ), + child: const SizedBox(height: 10) + ) + else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(5), + right: getProportionateScreenWidth(10), + ), + child: isExpanded2 + ? Html( + data: detailCourse.bio, + style: { + "body": Style( + fontSize: FontSize( + getProportionateScreenWidth(12)), + fontWeight: light, + fontFamily: 'Poppins', + ), + }, + ) + : Html( + data: detailCourse.bio != null && + detailCourse.bio!.length > 100 + ? detailCourse.bio!.substring(0, 200) + : detailCourse.bio!, + style: { + "body": Style( + fontSize: FontSize( + getProportionateScreenWidth(12)), + fontWeight: reguler, + fontFamily: 'Poppins', + ), + }, + ), + ), + if (detailCourse.bio!.isNotEmpty && + detailCourse.bio!.length > 100) + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(12), + bottom: getProportionateScreenHeight(10), + ), + child: GestureDetector( + onTap: () { + setState(() { + isExpanded2 = !isExpanded2; + print('asdasd'); + }); + }, + child: Text( + isExpanded2 + ? 'Tampilkan Lebih Sedikit' + : 'Tampilkan Lebih Banyak', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ], + ), + ], + ), + // Instruktur( + // id: detailCourse.instructorId ?? '1', + // bio: detailCourse.bio, + // instructor: detailCourse.instructor, + // rating: detailCourse.rating[0].avgRating.toString(), + // review: detailCourse.rating[0].totalReview ?? '', + // fotoProfile: detailCourse.fotoProfile, + // totalLesson: detailCourse.totalLesson, + // totalStudent: detailCourse.totalStudents, + // headline: detailCourse.headlineInstructor, + // ), + ], + ), + ), + ); + } else if (state.state == ResultState.Error) { + return Center( + child: Text('Terjadi Kesalahan'), + ); + } else { + return Center(child: Text('error')); + } + }); + } +} diff --git a/lib/screens/course/component/detail_quest_and_answer.dart b/lib/screens/course/component/detail_quest_and_answer.dart new file mode 100644 index 0000000..15447d7 --- /dev/null +++ b/lib/screens/course/component/detail_quest_and_answer.dart @@ -0,0 +1,400 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/providers/like_or_unlike_provider.dart'; +import 'package:initial_folder/providers/posting_qna_reply_provider.dart'; +import 'package:initial_folder/providers/qna_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/reply_qna_user_page.dart'; +import 'package:provider/provider.dart'; + +import '../../../get_it.dart'; +import '../../../models/comment_qna_model.dart'; +// import '../../../widgets/qna_user.dart'; + +final scaffoldKey = GlobalKey<ScaffoldState>(); + +class DetailQuestAndAnswer extends StatefulWidget { + const DetailQuestAndAnswer({ + Key? key, + required this.id, + required this.qnaDataModel, + required this.index, + required this.userId, + }) : super(key: key); + + final QnaDataModel qnaDataModel; + final id; + final int index; + final int userId; + + @override + State<DetailQuestAndAnswer> createState() => _DetailQuestAndAnswerState(); +} + +class _DetailQuestAndAnswerState extends State<DetailQuestAndAnswer> { + final _controller = TextEditingController(); + final provider = qnaGetIt<QnaProvider>(); + final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); + + + void onReplyDeleted(String idRep) { + setState(() { + // Temukan indeks balasan yang akan dihapus + final int indexToRemove = widget.qnaDataModel.comment.indexWhere((comment) => comment.idRep == idRep); + + // Jika balasan ditemukan, hapus dan perbarui jumlah komentar + if (indexToRemove != -1) { + widget.qnaDataModel.comment.removeAt(indexToRemove); + widget.qnaDataModel.countComment = (widget.qnaDataModel.countComment ?? 1) - 1; + } + }); + } + + @override + Widget build(BuildContext context) { + PostingQnaReplyProvider postingQnaReplyProvider = Provider.of<PostingQnaReplyProvider>(context); + LikeOrUnlikeProvider _likeOrUnlikeProvider = Provider.of<LikeOrUnlikeProvider>(context); + + likeOrUnlikes(int idQna) async { + final provider = qnaGetIt<QnaProvider>(); + if (await _likeOrUnlikeProvider.likeOrUnlike(idQna)) { + provider.getQna(widget.id); + print("Respon Baik"); + } + } + + return Scaffold( + key: _scaffoldKey, + appBar: AppBar( + title: Text( + 'Pertanyaan', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16), + letterSpacing: 0.2, + ), + ), + ), + body: GestureDetector( + child: Stack( + children: [ + ListView( + children: [ + // Tampilan pertanyaan utama + Padding( + padding: EdgeInsets.all(getProportionateScreenWidth(0)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(getProportionateScreenWidth(16)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + backgroundColor: primaryColor, + backgroundImage: widget.qnaDataModel.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage(widget.qnaDataModel.fotoProfile ?? '') as ImageProvider, + ), + SizedBox(width: getProportionateScreenWidth(8)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.qnaDataModel.username ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + color: Theme.of(context).colorScheme.onBackground, + ), + ), + Text( + widget.qnaDataModel.date ?? '', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).colorScheme.onBackground, + ), + ), + ], + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + if (widget.qnaDataModel.title != '') + Text( + widget.qnaDataModel.title!, + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(16), + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onBackground, + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Html( + data: widget.qnaDataModel.quest ?? 'Pertanyaan tidak tersedia', + style: { + "*": Style(margin: Margins.zero), + }, + ), + SizedBox(height: getProportionateScreenHeight(16)), + Row( + children: [ + kLike( + widget.qnaDataModel.selfLiked ?? false, + int.parse(widget.qnaDataModel.countLike ?? '0'), + () async { + await likeOrUnlikes(int.parse(widget.qnaDataModel.idQna.toString())); + + setState(() { + widget.qnaDataModel.selfLiked = !(widget.qnaDataModel.selfLiked ?? false); + widget.qnaDataModel.countLike = widget.qnaDataModel.selfLiked! + ? (int.parse(widget.qnaDataModel.countLike ?? '0') + 1).toString() + : (int.parse(widget.qnaDataModel.countLike ?? '0') - 1).toString(); + }); + }, + ), + SizedBox(width: getProportionateScreenWidth(13)), + kComment(widget.qnaDataModel.comment.length), + ], +), + ], + ), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + // Divider(), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + vertical: getProportionateScreenWidth(8), + ), + child: Text( + 'Balasan', + style: thirdTextStyle.copyWith( + color: Theme.of(context).colorScheme.onBackground, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16), + letterSpacing: 1, + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(13)), + ReplyQnaUserPage( + idCourse: widget.id, + idQna: widget.qnaDataModel.idQna ?? '', + userId: widget.userId, + onReplyDeleted: onReplyDeleted, + ), + SizedBox(height: getProportionateScreenHeight(65)), + ], + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? Color.fromARGB(255, 54, 61, 96) + : Color.fromARGB(255, 221, 221, 221), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + height: getProportionateScreenWidth(72), + width: double.infinity, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + vertical: getProportionateScreenWidth(16), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: SizeConfig.screenWidth * 0.65, + child: TextFormField( + scrollPhysics: AlwaysScrollableScrollPhysics(), + textAlignVertical: TextAlignVertical.center, + controller: _controller, + scrollPadding: EdgeInsets.zero, + cursorColor: secondaryColor, + maxLines: 1, + minLines: 1, + keyboardType: TextInputType.multiline, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + hintStyle: secondaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12), + ), + hintText: "Balas Pertanyaan", + ), + ), + ), + ElevatedButton( + onPressed: () async { + if (_controller.text.trim().isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: Colors.orange, + content: Text( + 'Ups, balasan masih kosong. isi dulu yuk!', + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + } else { + bool isSuccessful = await postingQnaReplyProvider.postQnaReply( + _controller.text, + widget.qnaDataModel.idQna.toString(), + ); + + if (isSuccessful) { + setState(() { + // Tambahkan balasan baru ke dalam komentar + widget.qnaDataModel.comment.add( + Comment( + textRep: _controller.text, + username: '', + createAt: DateTime.now().toString(), + ), + ); + }); + + provider.getQna(widget.id); + _controller.clear(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: Colors.green, + content: Text( + 'Balasan berhasil dikirim', + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: Colors.red, + content: Text( + 'Gagal mengirim balasan, silakan coba lagi', + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + } + } + }, + child: Text( + 'Kirim', + style: thirdTextStyle.copyWith( + color: Colors.white, + letterSpacing: 0.3, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + minimumSize: Size( + getProportionateScreenWidth(35), + getProportionateScreenWidth(35), + ), + ), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +Widget kLike(bool isLiked, int likeCount, Function onTap) { + return GestureDetector( + onTap: () => onTap(), + child: Row( + children: [ + Icon( + isLiked ? Icons.favorite : Icons.favorite_border_rounded, + color: isLiked ? Colors.red : secondaryColor, + size: 25, + ), + SizedBox(width: 5), + Text( + "$likeCount", + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.3, + ), + ), + ], + ), + ); +} + +Widget kComment(int? commentCount) { + return Row( + children: [ + Icon( + FontAwesomeIcons.comment, + color: secondaryColor, + size: 25, + ), + SizedBox(width: 5), + Text( + "${commentCount ?? 0}", + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.3, + ), + ), + ], + ); +} + + diff --git a/lib/screens/course/component/download_certificate.dart b/lib/screens/course/component/download_certificate.dart new file mode 100644 index 0000000..25c91a6 --- /dev/null +++ b/lib/screens/course/component/download_certificate.dart @@ -0,0 +1,32 @@ +import 'dart:js'; + +import 'package:flutter/material.dart'; +import 'package:flutter_downloader/flutter_downloader.dart'; +import 'package:initial_folder/screens/course/sertif.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/certificate_provider.dart'; + +class DownloadCertificate extends StatelessWidget { + final int? idCourse; + const DownloadCertificate({Key? key, this.idCourse}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: Column( + children: [ + Row( + children: [ + ElevatedButton(onPressed: () {}, child: Text('PNG')), + ElevatedButton(onPressed: () {}, child: Text('PDF')) + ], + ) + ], + ), + ); + } +} diff --git a/lib/screens/course/component/expansion_tile_copy.dart b/lib/screens/course/component/expansion_tile_copy.dart new file mode 100644 index 0000000..8788475 --- /dev/null +++ b/lib/screens/course/component/expansion_tile_copy.dart @@ -0,0 +1,487 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:initial_folder/theme.dart'; + +const Duration _kExpand = Duration(milliseconds: 200); + +/// A single-line [ListTile] with an expansion arrow icon that expands or collapses +/// the tile to reveal or hide the [children]. +/// +/// This widget is typically used with [ListView] to create an +/// "expand / collapse" list entry. When used with scrolling widgets like +/// [ListView], a unique [PageStorageKey] must be specified to enable the +/// [ExpansionTile] to save and restore its expanded state when it is scrolled +/// in and out of view. +/// +/// This class overrides the [ListTileThemeData.iconColor] and [ListTileThemeData.textColor] +/// theme properties for its [ListTile]. These colors animate between values when +/// the tile is expanded and collapsed: between [iconColor], [collapsedIconColor] and +/// between [textColor] and [collapsedTextColor]. +/// +/// The expansion arrow icon is shown on the right by default in left-to-right languages +/// (i.e. the trailing edge). This can be changed using [controlAffinity]. This maps +/// to the [leading] and [trailing] properties of [ExpansionTile]. +/// +/// {@tool dartpad} +/// This example demonstrates different configurations of ExpansionTile. +/// +/// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [ListTile], useful for creating expansion tile [children] when the +/// expansion tile represents a sublist. +/// * The "Expand and collapse" section of +/// <https://material.io/components/lists#types> +class ExpansionTileCopy extends StatefulWidget { + /// Creates a single-line [ListTile] with an expansion arrow icon that expands or collapses + /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must + /// be non-null. + const ExpansionTileCopy({ + Key? key, + this.leading, + required this.title, + this.subtitle, + this.onExpansionChanged, + this.children = const <Widget>[], + this.trailing, + this.initiallyExpanded = false, + this.maintainState = false, + this.tilePadding, + this.expandedCrossAxisAlignment, + this.expandedAlignment, + this.childrenPadding, + this.backgroundColor, + this.collapsedBackgroundColor, + this.textColor, + this.collapsedTextColor, + this.iconColor, + this.collapsedIconColor, + this.controlAffinity, + }) : assert( + expandedCrossAxisAlignment != CrossAxisAlignment.baseline, + 'CrossAxisAlignment.baseline is not supported since the expanded children ' + 'are aligned in a column, not a row. Try to use another constant.', + ), + super(key: key); + + /// A widget to display before the title. + /// + /// Typically a [CircleAvatar] widget. + /// + /// Note that depending on the value of [controlAffinity], the [leading] widget + /// may replace the rotating expansion arrow icon. + final Widget? leading; + + /// The primary content of the list item. + /// + /// Typically a [Text] widget. + final Widget title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + final Widget? subtitle; + + /// Called when the tile expands or collapses. + /// + /// When the tile starts expanding, this function is called with the value + /// true. When the tile starts collapsing, this function is called with + /// the value false. + final ValueChanged<bool>? onExpansionChanged; + + /// The widgets that are displayed when the tile expands. + /// + /// Typically [ListTile] widgets. + final List<Widget> children; + + /// The color to display behind the sublist when expanded. + /// + /// If this property is null then [ExpansionTileThemeData.backgroundColor] is used. If that + /// is also null then Colors.transparent is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? backgroundColor; + + /// When not null, defines the background color of tile when the sublist is collapsed. + /// + /// If this property is null then [ExpansionTileThemeData.collapsedBackgroundColor] is used. + /// If that is also null then Colors.transparent is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? collapsedBackgroundColor; + + /// A widget to display after the title. + /// + /// Note that depending on the value of [controlAffinity], the [trailing] widget + /// may replace the rotating expansion arrow icon. + final Widget? trailing; + + /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). + final bool initiallyExpanded; + + /// Specifies whether the state of the children is maintained when the tile expands and collapses. + /// + /// When true, the children are kept in the tree while the tile is collapsed. + /// When false (default), the children are removed from the tree when the tile is + /// collapsed and recreated upon expansion. + final bool maintainState; + + /// Specifies padding for the [ListTile]. + /// + /// Analogous to [ListTile.contentPadding], this property defines the insets for + /// the [leading], [title], [subtitle] and [trailing] widgets. It does not inset + /// the expanded [children] widgets. + /// + /// If this property is null then [ExpansionTileThemeData.tilePadding] is used. If that + /// is also null then the tile's padding is `EdgeInsets.symmetric(horizontal: 16.0)`. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final EdgeInsetsGeometry? tilePadding; + + /// Specifies the alignment of [children], which are arranged in a column when + /// the tile is expanded. + /// + /// The internals of the expanded tile make use of a [Column] widget for + /// [children], and [Align] widget to align the column. The `expandedAlignment` + /// parameter is passed directly into the [Align]. + /// + /// Modifying this property controls the alignment of the column within the + /// expanded tile, not the alignment of [children] widgets within the column. + /// To align each child within [children], see [expandedCrossAxisAlignment]. + /// + /// The width of the column is the width of the widest child widget in [children]. + /// + /// If this property is null then [ExpansionTileThemeData.expandedAlignment]is used. If that + /// is also null then the value of `expandedAlignment` is [Alignment.center]. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Alignment? expandedAlignment; + + /// Specifies the alignment of each child within [children] when the tile is expanded. + /// + /// The internals of the expanded tile make use of a [Column] widget for + /// [children], and the `crossAxisAlignment` parameter is passed directly into the [Column]. + /// + /// Modifying this property controls the cross axis alignment of each child + /// within its [Column]. Note that the width of the [Column] that houses + /// [children] will be the same as the widest child widget in [children]. It is + /// not necessarily the width of [Column] is equal to the width of expanded tile. + /// + /// To align the [Column] along the expanded tile, use the [expandedAlignment] property + /// instead. + /// + /// When the value is null, the value of `expandedCrossAxisAlignment` is [CrossAxisAlignment.center]. + final CrossAxisAlignment? expandedCrossAxisAlignment; + + /// Specifies padding for [children]. + /// + /// If this property is null then [ExpansionTileThemeData.childrenPadding] is used. If that + /// is also null then the value of `childrenPadding` is [EdgeInsets.zero]. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final EdgeInsetsGeometry? childrenPadding; + + /// The icon color of tile's expansion arrow icon when the sublist is expanded. + /// + /// Used to override to the [ListTileThemeData.iconColor]. + /// + /// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that + /// is also null then the value of [ListTileThemeData.iconColor] is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? iconColor; + + /// The icon color of tile's expansion arrow icon when the sublist is collapsed. + /// + /// Used to override to the [ListTileThemeData.iconColor]. + final Color? collapsedIconColor; + + /// The color of the tile's titles when the sublist is expanded. + /// + /// Used to override to the [ListTileThemeData.textColor]. + /// + /// If this property is null then [ExpansionTileThemeData.textColor] is used. If that + /// is also null then the value of [ListTileThemeData.textColor] is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? textColor; + + /// The color of the tile's titles when the sublist is collapsed. + /// + /// Used to override to the [ListTileThemeData.textColor]. + /// + /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used. If that + /// is also null then the value of [ListTileThemeData.textColor] is used. + /// + /// See also: + /// + /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s + /// [ExpansionTileThemeData]. + final Color? collapsedTextColor; + + /// Typically used to force the expansion arrow icon to the tile's leading or trailing edge. + /// + /// By default, the value of `controlAffinity` is [ListTileControlAffinity.platform], + /// which means that the expansion arrow icon will appear on the tile's trailing edge. + final ListTileControlAffinity? controlAffinity; + + @override + State<ExpansionTileCopy> createState() => _ExpansionTileCopyState(); +} + +class _ExpansionTileCopyState extends State<ExpansionTileCopy> + with SingleTickerProviderStateMixin { + static final Animatable<double> _easeOutTween = + CurveTween(curve: Curves.easeOut); + static final Animatable<double> _easeInTween = + CurveTween(curve: Curves.easeIn); + static final Animatable<double> _halfTween = + Tween<double>(begin: 0.0, end: 0.5); + + final ColorTween _borderColorTween = ColorTween(); + final ColorTween _headerColorTween = ColorTween(); + final ColorTween _iconColorTween = ColorTween(); + final ColorTween _backgroundColorTween = ColorTween(); + + late AnimationController _controller; + late Animation<double> _iconTurns; + late Animation<double> _heightFactor; + late Animation<Color?> _borderColor; + late Animation<Color?> _headerColor; + late Animation<Color?> _iconColor; + late Animation<Color?> _backgroundColor; + + bool _isExpanded = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController(duration: _kExpand, vsync: this); + _heightFactor = _controller.drive(_easeInTween); + _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); + _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween)); + _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); + _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); + _backgroundColor = + _controller.drive(_backgroundColorTween.chain(_easeOutTween)); + + _isExpanded = PageStorage.of(context)?.readState(context) as bool? ?? + widget.initiallyExpanded; + if (_isExpanded) { + _controller.value = 1.0; + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _handleTap() { + setState(() { + _isExpanded = !_isExpanded; + if (_isExpanded) { + _controller.forward(); + } else { + _controller.reverse().then<void>((void value) { + if (!mounted) { + return; + } + setState(() { + // Rebuild without widget.children. + }); + }); + } + PageStorage.of(context)?.writeState(context, _isExpanded); + }); + widget.onExpansionChanged?.call(_isExpanded); + } + + // Added to class + void closeExpansion() { + if (_isExpanded) _handleTap(); + } + + // Added to class + void openExpansion() { + if (!_isExpanded) _handleTap(); + } + + // Platform or null affinity defaults to trailing. + ListTileControlAffinity _effectiveAffinity( + ListTileControlAffinity? affinity) { + switch (affinity ?? ListTileControlAffinity.trailing) { + case ListTileControlAffinity.leading: + return ListTileControlAffinity.leading; + case ListTileControlAffinity.trailing: + case ListTileControlAffinity.platform: + return ListTileControlAffinity.trailing; + } + } + + Widget? _buildIcon(BuildContext context) { + return RotationTransition( + turns: _iconTurns, + child: const Icon(Icons.expand_more), + ); + } + + Widget? _buildLeadingIcon(BuildContext context) { + if (_effectiveAffinity(widget.controlAffinity) != + ListTileControlAffinity.leading) { + return null; + } + return _buildIcon(context); + } + + // Ubah trailing menjadi "+" dan "-" saat ExpansionTileCopy dibuka + Widget? _buildTrailingIcon(BuildContext context) { + if (_effectiveAffinity(widget.controlAffinity) != + ListTileControlAffinity.trailing) { + return null; + } + final Color trailingIconColor = Theme.of(context).brightness == Brightness.light + ? primaryColor + : primaryColorligtmode; + return _isExpanded + ? Icon( + Icons.remove, + color: trailingIconColor, + ) + : Icon( + Icons.add, + color: trailingIconColor, + ); + } + + Widget _buildChildren(BuildContext context, Widget? child) { + final ExpansionTileThemeData expansionTileTheme = + ExpansionTileTheme.of(context); + final Color borderSideColor = _borderColor.value ?? Colors.transparent; + + return Container( + decoration: BoxDecoration( + color: _backgroundColor.value ?? + expansionTileTheme.backgroundColor ?? + Colors.transparent, + border: Border( + top: BorderSide(color: borderSideColor), + bottom: BorderSide(color: borderSideColor), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: <Widget>[ + ListTileTheme.merge( + iconColor: _iconColor.value ?? expansionTileTheme.iconColor, + textColor: _headerColor.value, + child: ListTile( + onTap: _handleTap, + contentPadding: + widget.tilePadding ?? expansionTileTheme.tilePadding, + leading: widget.leading ?? _buildLeadingIcon(context), + title: widget.title, + subtitle: widget.subtitle, + trailing: widget.trailing ?? _buildTrailingIcon(context), + ), + ), + ClipRect( + child: Align( + alignment: widget.expandedAlignment ?? + expansionTileTheme.expandedAlignment ?? + Alignment.center, + heightFactor: _heightFactor.value, + child: child, + ), + ), + ], + ), + ); + } + + @override + void didChangeDependencies() { + final ThemeData theme = Theme.of(context); + final ExpansionTileThemeData expansionTileTheme = + ExpansionTileTheme.of(context); + final ColorScheme colorScheme = theme.colorScheme; + _borderColorTween.end = theme.dividerColor; + _headerColorTween + ..begin = widget.collapsedTextColor ?? + expansionTileTheme.collapsedTextColor ?? + theme.textTheme.subtitle1!.color + ..end = widget.textColor ?? + expansionTileTheme.textColor ?? + colorScheme.primary; + _iconColorTween + ..begin = widget.collapsedIconColor ?? + expansionTileTheme.collapsedIconColor ?? + theme.unselectedWidgetColor + ..end = widget.iconColor ?? + expansionTileTheme.iconColor ?? + colorScheme.primary; + _backgroundColorTween + ..begin = widget.collapsedBackgroundColor ?? + expansionTileTheme.collapsedBackgroundColor + ..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final ExpansionTileThemeData expansionTileTheme = + ExpansionTileTheme.of(context); + final bool closed = !_isExpanded && _controller.isDismissed; + final bool shouldRemoveChildren = closed && !widget.maintainState; + + final Widget result = Offstage( + offstage: closed, + child: TickerMode( + enabled: !closed, + child: Padding( + padding: widget.childrenPadding ?? + expansionTileTheme.childrenPadding ?? + EdgeInsets.zero, + child: Column( + crossAxisAlignment: + widget.expandedCrossAxisAlignment ?? CrossAxisAlignment.center, + children: widget.children, + ), + ), + ), + ); + + return AnimatedBuilder( + animation: _controller.view, + builder: _buildChildren, + child: shouldRemoveChildren ? null : result, + ); + } +} diff --git a/lib/screens/course/component/inside_announcement.dart b/lib/screens/course/component/inside_announcement.dart new file mode 100644 index 0000000..ee86c51 --- /dev/null +++ b/lib/screens/course/component/inside_announcement.dart @@ -0,0 +1,308 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/providers/announcement_provider.dart'; +import 'package:initial_folder/providers/posting_announcement_reply_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/announcement_user.dart'; +import 'package:initial_folder/widgets/reply_announcement_user_page.dart'; +import 'package:provider/provider.dart'; + +import '../../../get_it.dart'; + +final scaffoldKey = GlobalKey<ScaffoldState>(); + +class InsideAnnouncement extends StatefulWidget { + const InsideAnnouncement({ + Key? key, + required this.id, + required this.announcementDataModel, + required this.index, + required this.userId, + }) : super(key: key); + final AnnouncementDataModel announcementDataModel; + final id; + + final int index; + final int userId; + @override + State<InsideAnnouncement> createState() => _InsideAnnouncementState(); +} + +class _InsideAnnouncementState extends State<InsideAnnouncement> { + final _controller = TextEditingController(); + final provider = announcementGetIt<AnnouncementProvider>(); + late Widget announcement; + + @override + Widget build(BuildContext context) { + PostingAnnouncementReplyProvider postingAnnouncementReplyProvider = + Provider.of<PostingAnnouncementReplyProvider>(context); + + return Scaffold( + key: scaffoldKey, + appBar: AppBar( + title: Text( + 'Pengumuman', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16), + letterSpacing: 0.2, + ), + ), + ), + body: GestureDetector( + // onTap: () => FocusScope.of(context).unfocus(), + child: Stack( + children: [ + ListView( + children: [ + // QnaUser( + // qnaDataModel: widget.qnaDataModel, + // id: widget.id, + // index: widget.index, + // userId: widget.userId, + // ), + StreamBuilder<AnnouncementModel>( + stream: provider.announcementStream, + builder: + (context, AsyncSnapshot<AnnouncementModel> snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + announcement = Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + break; + case ConnectionState.none: + announcement = Center( + child: Text( + 'Tidak ada koneksi', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.active: + print('masuk siniiiiiiiiiii active' + + snapshot.data!.error.toString()); + announcement = snapshot.data!.data[0].length > 0 + ? Container( + padding: EdgeInsets.only(top: 12), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(10), + bottomLeft: Radius.circular(10)), + color: Theme.of(context) + .colorScheme + .primaryContainer, + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .brightness == + Brightness.dark + ? Color(0xff212643) + : Colors.grey, + blurRadius: 0.5, + offset: Offset(0, 2), + spreadRadius: 0.001) + ]), + child: AnnouncementUser( + announcementDataModel: + snapshot.data!.data[0][widget.index], + id: widget.id, + index: widget.index, + userId: widget.userId, + )) + : Center( + child: Text( + 'Belum ada pengumuman', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.done: + announcement = snapshot.data!.data[0].length > 0 + ? AnnouncementUser( + announcementDataModel: + snapshot.data!.data[0][widget.index], + id: widget.id, + index: widget.index, + userId: widget.userId, + ) + : Center( + child: Text( + 'Belum ada pertanyaan', + style: thirdTextStyle, + ), + ); + break; + } + } + return announcement; + }), + + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + vertical: getProportionateScreenWidth(8)), + child: InkWell( + onTap: () {}, + child: Text( + 'Balasan', + style: thirdTextStyle.copyWith( + color: Theme.of(context).colorScheme.onBackground, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16), + letterSpacing: 1), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(13)), + ReplyAnnouncementUserPage( + idCourse: widget.id, + index: widget.index, + userId: widget.userId, + ), + SizedBox(height: 50) + ], + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? Color.fromARGB(255, 54, 61, 96) + : Color.fromARGB(255, 221, 221, 221), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20))), + height: getProportionateScreenWidth(72), + width: double.infinity, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + vertical: getProportionateScreenWidth(16), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: SizeConfig.screenWidth * 0.65, + child: TextFormField( + controller: _controller, + scrollPadding: EdgeInsets.zero, + cursorColor: secondaryColor, + decoration: InputDecoration( + filled: true, + fillColor: + Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), + borderSide: BorderSide.none), + hintStyle: secondaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12), + ), + hintText: "Balas", + ), + ), + ), + ElevatedButton( + onPressed: () async { + if (await postingAnnouncementReplyProvider + .postAnnouncementReply( + _controller.text, + widget.announcementDataModel.idAnnouncement + .toString(), + widget.announcementDataModel.tokenAnnouncement + .toString(), + widget.announcementDataModel.idAnnouncement + .toString(), + )) { + provider.getAnnouncement(widget.id); + _controller.clear(); + ScaffoldMessenger.of(scaffoldKey.currentContext!) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Balasan Terkirim', + style: primaryTextStyle.copyWith( + color: backgroundColor), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + action: SnackBarAction( + label: 'Lihat', + onPressed: () { + ScaffoldMessenger.of( + scaffoldKey.currentContext!) + .hideCurrentSnackBar(); + }, + ), + ), + ); + } else { + ScaffoldMessenger.of(scaffoldKey.currentContext!) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Terjadi kesalahan', + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } + }, + child: Text( + 'Kirim', + style: thirdTextStyle.copyWith( + color: Colors.white, + letterSpacing: 0.3, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + minimumSize: Size( + getProportionateScreenWidth(35), + getProportionateScreenWidth(35), + ), + ), + ) + ], + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/course/component/pdfReader.dart b/lib/screens/course/component/pdfReader.dart new file mode 100644 index 0000000..21f58e1 --- /dev/null +++ b/lib/screens/course/component/pdfReader.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:easy_pdf_viewer/easy_pdf_viewer.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class pdfReader extends StatefulWidget { + final String link; + final String title; + const pdfReader({super.key, required this.link, required this.title}); + + @override + State<pdfReader> createState() => _pdfReaderState(); +} + +class _pdfReaderState extends State<pdfReader> { + late PDFDocument document; + bool _isLoading = true; + @override + void initState() { + super.initState(); + // Load from URL + loadPDF(); + } + + void loadPDF() async { + document = await PDFDocument.fromURL(widget.link); + setState(() { + _isLoading = false; + }); + } + + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + widget.title, + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16)), + ), + ), + body: Center( + child: _isLoading + ? Center(child: CircularProgressIndicator()) + : PDFViewer( + document: document, + pickerButtonColor: primaryColor, + )), + ); + } +} diff --git a/lib/screens/course/component/quest_and_answer.dart b/lib/screens/course/component/quest_and_answer.dart new file mode 100644 index 0000000..cc157dd --- /dev/null +++ b/lib/screens/course/component/quest_and_answer.dart @@ -0,0 +1,338 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/main.dart'; +import 'package:initial_folder/providers/qna_provider.dart'; +import 'package:initial_folder/services/qna_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/providers/posting_qna_provider.dart'; +import 'package:initial_folder/widgets/qna_user_page.dart'; +import 'package:provider/provider.dart'; +import 'package:quill_html_editor/quill_html_editor.dart'; + +class QuestAndAnswer extends StatefulWidget { + const QuestAndAnswer({ + Key? key, + required this.id, + required this.idLesson, + }) : super(key: key); + + final id; + final idLesson; + + @override + State<QuestAndAnswer> createState() => _QuestAndAnswerState(); +} + +class _QuestAndAnswerState extends State<QuestAndAnswer> { + double value = 0; + + final _controllerTitle = TextEditingController(); + final _controller = TextEditingController(); + final QuillEditorController _controllerQuest = QuillEditorController(); + final customToolbar = [ + ToolBarStyle.headerOne, + ToolBarStyle.headerTwo, + ToolBarStyle.bold, + ToolBarStyle.italic, + ToolBarStyle.underline, + ToolBarStyle.color, + ToolBarStyle.listBullet, + ToolBarStyle.listOrdered, + ]; + + @override + void initState() { + // TODO: implement initState + QnaService().getMyQna(widget.id); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + PostingQnaProvider postingQnaProvider = + Provider.of<PostingQnaProvider>(context); + return SingleChildScrollView( + child: GestureDetector( + onTap: () => FocusScope.of(context).unfocus(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenWidth(16), + ), + Text( + 'Ajukan Pertanyaan', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 0.1, + fontSize: getProportionateScreenWidth(16), + ), + ), + SizedBox( + height: getProportionateScreenWidth(8), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: + Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Colors.grey, + blurRadius: 0.5, + offset: Offset(0, 2), + spreadRadius: 0.001) + ]), + child: TextField( + controller: _controllerTitle, + cursorColor: secondaryColor, + scrollPadding: EdgeInsets.zero, + decoration: InputDecoration( + filled: true, + fillColor: + Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), + borderSide: BorderSide.none), + hintStyle: secondaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12), + ), + hintText: "Masukan Judul Pertanyaan", + ), + ), + ), + SizedBox( + height: getProportionateScreenWidth(18), + ), + ToolBar( + toolBarColor: + Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200]!, + activeIconColor: primaryColor, + iconColor: secondaryColor, + padding: const EdgeInsets.all(8), + iconSize: 20, + controller: _controllerQuest, + toolBarConfig: customToolbar, + ), + Container( + height: 180, + width: double.infinity, + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + color: + Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Colors.grey, + blurRadius: 0.5, + offset: Offset(0, 2), + spreadRadius: 0.001) + ]), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: QuillHtmlEditor( + hintText: 'Ketik pertanyaan mu', + controller: _controllerQuest, + isEnabled: true, + minHeight: 100, + backgroundColor: Theme.of(context).brightness == + Brightness.dark + ? seventeenColor + : Colors.grey[200]!, + textStyle: secondaryTextStyle.copyWith( + color: secondaryColor, + fontSize: getProportionateScreenWidth(16), + ), + hintTextStyle: secondaryTextStyle.copyWith( + color: secondaryColor, + fontSize: getProportionateScreenWidth(16), + ), + hintTextAlign: TextAlign.start, + padding: const EdgeInsets.all(3), + hintTextPadding: const EdgeInsets.all(0), + loadingBuilder: (context) { + return const Center( + child: CircularProgressIndicator( + strokeWidth: 0.4, + )); + }, + ), + ), + // TextField( + // controller: _controller, + // cursorColor: secondaryColor, + // scrollPadding: EdgeInsets.zero, + // minLines: 2, + // keyboardType: TextInputType.multiline, + // maxLines: 2, + // decoration: InputDecoration( + // filled: true, + // fillColor: Theme.of(context).brightness == + // Brightness.dark + // ? seventeenColor + // : Colors.grey[200], + // border: OutlineInputBorder( + // borderRadius: BorderRadius.circular( + // 10, + // ), + // borderSide: BorderSide.none), + // hintStyle: secondaryTextStyle.copyWith( + // color: secondaryColor, + // letterSpacing: 0.5, + // fontSize: getProportionateScreenWidth(12), + // ), + // hintText: "Ketikkan Pertanyaanmu disini", + // ), + // ), + Align( + alignment: Alignment.topRight, + child: ElevatedButton( + onPressed: () async { + if (await postingQnaProvider.postingQna( + _controllerTitle.text, + await _controllerQuest.getText(), + widget.id, + widget.idLesson)) { + _controllerQuest.clear(); + _controllerTitle.clear(); + ScaffoldMessenger.of( + globalScaffoldKey.currentContext!) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Pertanyaan berhasil dikirimkan', + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + action: SnackBarAction( + label: 'Lihat', + onPressed: () { + ScaffoldMessenger.of( + globalScaffoldKey + .currentContext!) + .hideCurrentSnackBar(); + }, + ), + ), + ); + } else { + ScaffoldMessenger.of( + globalScaffoldKey.currentContext!) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Terjadi kesalahan', + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + ), + ); + } + }, + child: Text( + 'Kirim', + style: thirdTextStyle.copyWith( + color: Colors.white, + fontSize: SizeConfig.blockHorizontal! * 4, + ), + ), + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12)), + backgroundColor: primaryColor, + ), + ), + ), + ], + )), + ), + ], + ), + ], + ), + ), + SizedBox( + height: 15, + ), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Text( + 'Pertanyaan Dan Jawaban', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(16), + ), + ), + ), + SizedBox( + height: getProportionateScreenHeight(14), + ), + QnaUserPage(idCourse: widget.id), + SizedBox( + height: getProportionateScreenHeight(14), + ), + // QandA( + // divider: Divider(), + // ), + // QandA( + // divider: Divider(), + // ), + ], + ), + ), + ); + } + + @override + void dispose() { + _controller.dispose(); + + super.dispose(); + } +} diff --git a/lib/screens/course/component/txtReader.dart b/lib/screens/course/component/txtReader.dart new file mode 100644 index 0000000..adcc144 --- /dev/null +++ b/lib/screens/course/component/txtReader.dart @@ -0,0 +1,46 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:easy_pdf_viewer/easy_pdf_viewer.dart'; +import 'package:flutter_text_viewer/model/text_viewer.dart'; +import 'package:flutter_text_viewer/screen/text_viewer_page.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class txtReader extends StatefulWidget { + final String link; + // final String title; + const txtReader({Key? key, required this.link}); + + @override + State<txtReader> createState() => _txtReaderState(); +} + +class _txtReaderState extends State<txtReader> { + @override + void initState() { + super.initState(); + print(widget.link); + } + + @override + void dispose() { + super.dispose(); + // Hapus file .txt saat widget di-dispose + File(widget.link).deleteSync(recursive: true); + } + + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: TextViewerPage( + textViewer: TextViewer.asset( + widget.link, + highLightColor: Colors.yellow, + focusColor: Colors.orange, + ignoreCase: true, + ), + showSearchAppBar: true, + ))); + } +} diff --git a/lib/screens/course/html5_video_page.dart b/lib/screens/course/html5_video_page.dart new file mode 100644 index 0000000..c774b28 --- /dev/null +++ b/lib/screens/course/html5_video_page.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:chewie/chewie.dart'; +import 'package:video_player/video_player.dart'; + +class Html5Vid extends StatefulWidget { + const Html5Vid({Key? key, required this.link, required this.title}); + final String link; + final String title; + @override + State<Html5Vid> createState() => Html5VidState(); +} + +class Html5VidState extends State<Html5Vid> { + late VideoPlayerController _videoPlayerController; + late ChewieController _chewieController; + bool _isVideoLoading = true; + + @override + void initState() { + super.initState(); + print(widget.link); + print(widget.title); + _videoPlayerController = VideoPlayerController.network('${widget.link}'); + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + aspectRatio: 16 / 9, + autoPlay: true, + looping: false, + ); + + _videoPlayerController.addListener(() { + if (_videoPlayerController.value.isInitialized && + !_videoPlayerController.value.isBuffering) { + setState(() { + _isVideoLoading = false; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: _onWillPop, + child: Scaffold( + appBar: AppBar( + title: Text('${widget.title}'), + ), + body: Stack( + children: [ + Center( + child: Chewie( + controller: _chewieController, + ), + ), + if (_isVideoLoading) + Center( + child: CircularProgressIndicator(), + ), + ], + ), + ), + ); + } + + Future<bool> _onWillPop() async { + await _videoPlayerController.pause(); + await _videoPlayerController.dispose(); + // await _chewieController.dispose(); + return true; + } + + @override + void dispose() { + _videoPlayerController.dispose(); + _chewieController.dispose(); + super.dispose(); + } +} diff --git a/lib/screens/course/my_course_page.dart b/lib/screens/course/my_course_page.dart new file mode 100644 index 0000000..3d1bceb --- /dev/null +++ b/lib/screens/course/my_course_page.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/course/search_my_course_page.dart'; +import 'package:initial_folder/screens/home/components/body_comp/latest_course.dart'; +import 'package:initial_folder/screens/home/components/body_comp/populer_course.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/widgets/loading/loading_my_course.dart'; +import 'package:initial_folder/widgets/my_course_list.dart'; +import 'package:provider/provider.dart'; +import '../../theme.dart'; + +class MyCoursePage extends StatelessWidget { + const MyCoursePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + Widget notLogin() { + return Center( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(40), + ), + child: Text( + 'Kamu belum login, silahkan login untuk melihat kursus mu', + textAlign: TextAlign.center, + style: primaryTextStyle, + ), + ), + ); + } + + Widget myCourse() { + return RefreshIndicator( + displacement: 40, + color: primaryColor, + onRefresh: () async { + await Provider.of<MyCourseProvider>(context, listen: false) + .getMyCourse(); + }, + child: Consumer<MyCourseProvider>( + builder: (BuildContext context, state, _) { + if (state.state == ResultState.Loading) { + return Column( + children: [ + LoadingMyCourse(), + LoadingMyCourse(), + LoadingMyCourse(), + ], + ); + } else if (state.state == ResultState.HasData) { + return Container( + margin: + EdgeInsets.only(bottom: getProportionateScreenHeight(4)), + child: ListView.builder( + shrinkWrap: true, + itemCount: state.result!.data[0].length, + itemBuilder: (context, index) { + var myCourse = state.result!.data[0][index]; + + return MyCourseList( + dataMyCourseModel: myCourse, + ); + }, + ), + ); + } else if (state.state == ResultState.NoData) { + return ListView( + children: [ + Container( + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(47)), + Container( + width: getProportionateScreenWidth(120), + height: getProportionateScreenWidth(120), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + ), + child: Image.asset( + 'assets/images/kursuskosong.png', + scale: 1, + color: Theme.of(context).colorScheme.onBackground, + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Text( + "Kursus tidak tersedia", + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + color: tenthColor, + ), + ), + SizedBox(height: getProportionateScreenHeight(3)), + Container( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Text( + "Kamu belum memiliki kursus, daftar kursus sekarang agar kamu dapat mengikuti kursus", + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + PopulerCourse(text: 'Kursus Teratas'), + LatestCourse(text: 'Kursus Terbaru'), + ], + ), + ), + ), + ], + ); + } else if (state.state == ResultState.Error) { + return Center( + child: ListView( + children: [ + Container( + padding: const EdgeInsets.all(20.0), + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height / 1.5), + child: Center( + child: Text('Terjadi Kesalahan'), + ), + ) + ], + ), + ); + } else { + return Center(child: Text('')); + } + }, + ), + ); + } + + return Scaffold( + appBar: (Condition.loginEmail || Condition.loginFirebase) + ? AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + 'Kursusku', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(20), + ), + ), + actions: [ + IconButton( + onPressed: () { + Provider.of<MyCourseProvider>(context, listen: false) + .clearSearch(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SearchMyCourse(), + ), + ); + }, + icon: Icon( + FeatherIcons.search, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + ], + ) + : AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + 'Kursusku', + style: secondaryTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 2.3, + fontSize: getProportionateScreenWidth(16), + ), + ), + ), + body: (Condition.loginEmail || Condition.loginFirebase) + ? myCourse() + : notLogin(), + ); + } +} diff --git a/lib/screens/course/play_course_page.dart b/lib/screens/course/play_course_page.dart new file mode 100644 index 0000000..f0537d8 --- /dev/null +++ b/lib/screens/course/play_course_page.dart @@ -0,0 +1,1974 @@ +//ini versi ketiga. + +// import 'dart:html'; + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:easy_image_viewer/easy_image_viewer.dart'; +import 'package:floating/floating.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/main.dart'; +import 'package:http/http.dart' as http; +import 'package:flutter_text_viewer/flutter_text_viewer.dart'; +import 'package:initial_folder/models/lesson_model.dart'; + +import 'package:initial_folder/models/section_model.dart'; +import 'package:initial_folder/providers/certificate_provider.dart' + as certifProvider; +import 'package:initial_folder/providers/current_lesson_provider.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; +import 'package:initial_folder/providers/play_video_course_provider.dart'; +import 'package:initial_folder/providers/selected_title_provider.dart'; +import 'package:initial_folder/screens/course/component/announcement.dart'; + +import 'package:initial_folder/screens/course/component/detail_play_course.dart'; +import 'package:initial_folder/screens/course/component/pdfReader.dart'; +import 'package:initial_folder/screens/course/component/quest_and_answer.dart'; +import 'package:initial_folder/screens/course/component/txtReader.dart'; +import 'package:initial_folder/screens/course/quiz_page.dart'; +import 'package:initial_folder/screens/course/sertif.dart'; +import 'package:initial_folder/services/current_lesson_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_expansion_tile.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:chewie/chewie.dart'; +import 'package:video_player/video_player.dart'; +import './component/expansion_tile_copy.dart'; + +class PlayCourse extends StatefulWidget { + const PlayCourse( + {Key? key, + required this.instruktur, + required this.judul, + required this.thumbnail, + required this.courseeid, + this.isQna}) + : super(key: key); + final String judul, instruktur, thumbnail, courseeid; + final bool? isQna; + + @override + State<PlayCourse> createState() => _PlayCoursePageState(); +} + +class _PlayCoursePageState extends State<PlayCourse> + with SingleTickerProviderStateMixin, WidgetsBindingObserver { + late TabController _tabController; + LessonCourseService lessonCourseService = LessonCourseService(); + + late YoutubePlayerController _controller; + + late PlayerState _playerState; + late YoutubeMetaData _videoMetaData; + + Duration _currentTime = const Duration(); + String? _setLocalLEssonId = ""; + int _ytCurrentTime = 0; + bool _isPlayerReady = false; + int totalProgress = 0; + bool _isvideoClicked = false; + bool _isyt = true; + String linkhtml5 = ''; + bool _gabisaskip = false; + List<DataLesson> dataLesson = []; + final floating = Floating(); + var _selectedIndex = 0; + int? _expandedTileIndex; + final _tileKeys = []; + + // Mengubah format durasi dari "hh:mm:ss" menjadi format yang lebih singkat (misal: 1j2m3d) + String formatDuration(String duration) { + if (duration == '') { + String formattedDuration = ''; + + print('masuk sini'); + formattedDuration += ''; + return formattedDuration; + } + List<String> parts = duration.split(':'); + int hours = int.parse(parts[0]); + int minutes = int.parse(parts[1]); + int seconds = int.parse(parts[2]); + + String formattedDuration = ''; + + if (hours != 0) { + formattedDuration += '${hours}j'; + } + + if (minutes != 0) { + formattedDuration += '${int.parse(parts[1])}m'; + } + + formattedDuration += '${int.parse(parts[2])}d'; + + return formattedDuration; + } + + String courseID_local_end = ''; + String lessonID_local_end = ''; + late ChewieController _chewieController; + VideoPlayerController _videoPlayerController = VideoPlayerController.network( + 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); + + // Inisialisasi controller dan pengaturan orientasi layar + @override + void initState() { + super.initState(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + WidgetsBinding.instance.addObserver(this); + _tabController = TabController( + vsync: this, length: 4, initialIndex: (widget.isQna == true) ? 2 : 0); + print( + "----------------------------------------------------------------> setup"); + _controller = YoutubePlayerController( + //todo + initialVideoId: "", + flags: const YoutubePlayerFlags( + mute: false, + disableDragSeek: false, + loop: false, + isLive: false, + forceHD: false, + enableCaption: false, + autoPlay: true, + hideThumbnail: true, + startAt: 0, + ), + )..addListener(() { + setState(() { + _playerState = _controller.value.playerState; + _currentTime = _controller.value.position; + }); + + if (_playerState == PlayerState.paused) { + () async { + await LessonCourseService().updateLessonCourse(_setLocalLEssonId, + progress: _currentTime.inSeconds); + }(); + } + }); + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoPlay: true, + looping: false, + aspectRatio: 16 / 9, + // Other ChewieController configurations... + ); + } + + // Inisialisasi YoutubePlayerController dengan url baru + void _initializeytController(String url) async { + setState(() { + _controller = YoutubePlayerController( + //todo + initialVideoId: YoutubePlayer.convertUrlToId(url).toString(), + flags: const YoutubePlayerFlags( + mute: false, + disableDragSeek: false, + loop: false, + isLive: false, + forceHD: false, + enableCaption: false, + autoPlay: true, + hideThumbnail: true, + ), + ); + ; + }); + } + + // Inisialisasi ChewieController untuk video HTML5 + void _initializeChewieController(String videoUrl) async { + _videoPlayerController = VideoPlayerController.network(videoUrl); + // await _videoPlayerController.initialize(); + + setState(() { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoPlay: true, + looping: false, + aspectRatio: 16 / 9, + additionalOptions: (context) { + return <OptionItem>[ + OptionItem( + onTap: () { + enablePip(); + Navigator.pop(context); + }, + iconData: Icons.picture_in_picture, + title: 'Aktifkan mode PiP', + ), + ]; + }, + // customControls: CustomChewieControls() + // Other ChewieController configurations... + ); + //todo + _videoPlayerController.addListener(() async { + if (_videoPlayerController.value.position == + _videoPlayerController.value.duration) { + print('video Ended'); + print(widget.courseeid); + print(lessonID_local_end); + LessonCourseService() + .updateLessonCourse(lessonID_local_end) + .then((value) => { + if (value == 201) {print('berhasil')} + }); + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + _gabisaskip = false; + + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonID_local_end) + .first + .isFinished = 1; + }); + + // print('g masuk'); + } + }); + }); + } + + // Membersihkan resource saat widget dihapus dari tree + @override + void dispose() { + () async { + await LessonCourseService().updateLessonCourse(_setLocalLEssonId, + progress: _currentTime.inSeconds); + + await _videoPlayerController.dispose(); + + }(); + _controller.dispose(); + _chewieController.dispose(); + WidgetsBinding.instance.removeObserver(this); + floating.dispose(); + + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + super.dispose(); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + } + + // Mengatur ulang status bar ketika aplikasi kembali dari background + @override + void didChangeAppLifecycleState(AppLifecycleState lifecycleState) { + super.didChangeAppLifecycleState(lifecycleState); + // if (lifecycleState == AppLifecycleState.inactive) { + // floating.enable(Rational.landscape()); + // } + if (lifecycleState == AppLifecycleState.resumed) { + // Aplikasi kembali dari background (termasuk dari mode PiP) + // Menampilkan status bar kembali + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + } + } + + // Mengaktifkan mode Picture-in-Picture (PiP) + Future<void> enablePip() async { + final status = await floating.enable(Rational.landscape()); + debugPrint('Pipenable? $status'); + } + + // Pause video ketika halaman dinonaktifkan (misal: navigasi ke halaman lain) + @override + void deactivate() { + // Pauses video while navigating to next page. + _controller.pause(); + // _chewieController.pause(); + super.deactivate(); + } + + // void listener() { + // if (_isPlayerReady && mounted && !_controller.value.isFullScreen) { + // setState(() { + // _playerState = _controller.value.playerState; + // _videoMetaData = _controller.metadata; + // }); + // } + // if (_playerState == PlayerState.ended) { + // print('Video telah selesai diputar'); + // } + // } + + // Menampilkan gambar attachment dalam image viewer + void loadImage(String link) async { + final imageProvider = + Image.network("https://api.vokasia.id/uploads/lesson_files/" + link) + .image; + showImageViewer(context, imageProvider, doubleTapZoomable: true, + onViewerDismissed: () { + print("dismissed"); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + }); + } + + // Mendownload dan membuka file attachment dari URL + Future<void> openFileFromUrl(BuildContext context, String fileUrl) async { + try { + print(fileUrl); + final response = await http.get( + Uri.parse('https://api.vokasia.id/uploads/lesson_files/$fileUrl')); + if (response.statusCode == 200) { + final fileBytes = response.bodyBytes; + final fileName = fileUrl.split('/').last; + final fileType = 'txt'; // Sesuaikan dengan tipe file yang ingin dibuka + + final tempDir = await getTemporaryDirectory(); + final tempFilePath = '${tempDir.path}/$fileName'; + + await File(tempFilePath).writeAsBytes(fileBytes); + print(tempFilePath); + OpenFile.open(tempFilePath); + // Buka file menggunakan aplikasi default pada perangkat + // Contoh: OpenFile.open(tempFilePath); + } else { + print('Failed to download file: ${response.statusCode}'); + } + } catch (e) { + print('Error opening file from URL: $e'); + } + } + + // Reset key ExpansionTile dan selectedIndex + void resetExpansionTileKeysAndSelectedIndex() { + _tileKeys.clear(); + _selectedIndex = 0; + } + + // Menghapus tag HTML dari string + String _parseHtmlString(String htmlText) { + RegExp exp = RegExp(r"<[^>]*>| |&|"", + multiLine: true, caseSensitive: true); + return htmlText.replaceAll(exp, ''); + } + + // Membuat Tab widget untuk TabBar + _getTab(index, child) { + return Tab( + height: 30, + iconMargin: EdgeInsets.zero, + child: Container( + child: Center( + child: Text( + child, + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.2, + letterSpacing: 0.4, + ), + ), + ), + ), + ); + } + + // Menandai lesson sebagai selesai (update ke server) + _ceklisattachment( + String lessonId, + String idCourse, + int isFinished, + ) async { + print('ini lesson id ->' + lessonId); + print('ini course id ->' + idCourse); + await lessonCourseService.updateLessonCourse(lessonId).then((value) => { + print('ini value dari sana' + value.toString()), + if (value == '201') + setState(() { + { + isFinished = 1; + } + }) + }); + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonId) + .first + .isFinished = 1; + }); + } + + // Widget cekbox( + // int isFinished, String isSkipped, String courseId, String lessonId) { + // return Checkbox( + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + // activeColor: primaryColor, + // value: (isFinished == 1) ? true : false, + // onChanged: (value) { + // if (isSkipped == '1') { + // Provider.of<LessonCourseProvider>(context) + // .updateLessonCourse(courseId, lessonId); + // } + // }); + // } + + // Mengubah string durasi ke Duration + Duration getVideoDuration(String duration) { + int hours = 0; + int minutes = 0; + int seconds = 0; + List<String> timeSplit = duration.split(':'); + hours = int.parse(timeSplit[0]); + minutes = int.parse(timeSplit[1]); + seconds = int.parse(timeSplit[2]); + return Duration(hours: hours, minutes: minutes, seconds: seconds - 1); + } + + // Widget Checkbox untuk menandai lesson selesai atau tidak + Widget cekbox( + BuildContext context, + int isFinished, + String isSkipped, + String lessonId, + Map<String?, dynamic> lessonMapId, + List<DataLesson> dataLesson, + String lessontypeee, + String videotypeee, + String idCourse) { + final playVideoCourseProvider = Provider.of<PlayVideoCourseProvider>(context, listen: false); + return Theme( + data: Theme.of(context).copyWith(unselectedWidgetColor: Colors.grey), + child: Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4)), + activeColor: Theme.of(context).brightness == Brightness.light + ? primaryColorligtmode + : primaryColor, + checkColor: Colors.white, + value: (isFinished == 1) ? true : false, + onChanged: (value) async { + // print(lessontypeee); + var data = dataLesson + .map((e) => e) + .where((lesson) => + lesson.lessonId == + lessonMapId['${playVideoCourseProvider.url}']) + .first; + if (lessontypeee != 'video') { + //todo + if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Anda tidak dapat mencentang pelajaran\nyang tidak dapat diskip', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } else { + print('ini lesson id ->' + lessonId); + print('ini course id ->' + idCourse); + await lessonCourseService + .updateLessonCourse(lessonId) + .then((value) => { + print('ini value dari sana' + value.toString()), + if (value == '201') + setState(() { + { + isFinished = 1; + } + }) + }); + await Provider.of<myCourseProvider.MyCourseProvider>( + context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonId) + .first + .isFinished = 1; + }); + } + } else if (videotypeee == 'html5') { + print('ceklis html5 diklik'); + + if (isSkipped == '1') { + _chewieController.seekTo(getVideoDuration(data.duration!)); + } else if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + } + + if (lessonId == lessonMapId['${playVideoCourseProvider.url}'] && + lessonId != '') { + if (isSkipped == '1') { + print('masuk kesini'); + _controller.seekTo(getVideoDuration(data.duration!)); + } else if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + } + })); + } + + // Widget ListTile untuk setiap bab/section kursus + Widget tileKursus( + BuildContext context, + Key tileKeyoper, + int index, + List lesonOper, + Iterable<Datum> sectionOper, + Datum e, + Map<String?, dynamic> lessonMapIdoper, + List<DataLesson> dataLessonOper) { + final selectedTitleProvider = Provider.of<SelectedTitleProvider>(context, listen: false); + final playVideoCourseProvider = Provider.of<PlayVideoCourseProvider>(context, listen: false); + return ListTileTheme( + dense: true, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 15, vertical: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + spreadRadius: 0.01, + blurRadius: 1, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: ExpansionTileCopy( + key: tileKeyoper, + initiallyExpanded: index == _expandedTileIndex, + onExpansionChanged: (value) { + // If tile is expanding, then collapse the already expanded tile. + if (value) { + if (index != _selectedIndex) { + _tileKeys[_selectedIndex].currentState!.closeExpansion(); + } + _selectedIndex = index; + } + }, + title: Text( + 'Bab ${index + 1}', + style: thirdTextStyle.copyWith( + fontWeight: bold, + letterSpacing: 0.5, + color: Theme.of(context).colorScheme.onBackground, + fontSize: getProportionateScreenWidth(12)), + ), + subtitle: Html( + shrinkWrap: true, + data: e.sectionTitle, + style: { + "body": Style( + margin: Margins.zero, + padding: HtmlPaddings.zero, + fontSize: FontSize(getProportionateScreenWidth(12)), + fontWeight: semiBold, + letterSpacing: 0.5, + fontFamily: 'Poppins', + color: Theme.of(context).colorScheme.onBackground), + }, + ), + children: e.dataLesson! + .asMap() + .entries + .map( + (e) => Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(20), + bottom: getProportionateScreenHeight(5)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10)), + child: GestureDetector( + onTap: _isPlayerReady + ? (e.value.videoType == 'youtubelink' || + e.value.videoType == 'YouTube') + ? () async { + + LessonModel currentLesson = + await CurrentLessonService() + .getCurrentLesson( + e.value.lessonId!); + + setState(() { + _ytCurrentTime = (double.tryParse(currentLesson.progressVideo ?? '0.0') ?? 0.0).round(); + _isvideoClicked = true; + _isPlayerReady = true; + _setLocalLEssonId = e.value.lessonId; + }); + print('masuk sini $_ytCurrentTime'); + selectedTitleProvider.selectedTitle = + e.value.title; + Announcement announcement = Announcement( + id: lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + lesonOper: lesonOper, + sectionOper: sectionOper, + lessonMapIdoper: lessonMapIdoper, + dataLessonOper: dataLessonOper, + ); + announcement.showSummary(e.value.title); + print(_gabisaskip); + if (_gabisaskip == true) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + )); + } else { + if (e.value.isSkip == '1') { + setState(() { + + + _gabisaskip = false; + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url, + ); + _isyt = true; + } else { + print( + "MASUK SINI KAH?? ---------------------> $_ytCurrentTime"); + print( + "link ------------------------------------------------>${YoutubePlayer.convertUrlToId(playVideoCourseProvider.url)!}"); + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider.url)!, + startAt: _ytCurrentTime, + ); + _isyt = true; + } + _isyt = true; + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 1) { + setState(() { + + + _gabisaskip = false; + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url, + ); + _isyt = true; + } else { + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider + .url) ?? + '', + startAt: _ytCurrentTime, + ); + _isyt = true; + } + _isyt = true; + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 0) { + //todo + setState(() { + _gabisaskip = true; + + + + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url, + ); + _isyt = true; + } else { + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider + .url) ?? + '', + startAt: _ytCurrentTime, + ); + _isyt = true; + } + _isyt = true; + }); + } + } + } + : (e.value.videoType == 'html5') + ? () { + setState(() { + _isvideoClicked = true; + _isPlayerReady = true; + }); + print(_gabisaskip); + if (_gabisaskip == true) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + )); + } else { + if (e.value.isSkip == '1') { + setState(() { + _gabisaskip = false; + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 1) { + setState(() { + _gabisaskip = false; + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 0) { + //todo + setState(() { + _gabisaskip = true; + + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } + } + } + : () { + (e.value.lessonType == 'quiz') + ? { + print('ini quiz'), + (_isyt == true) + ? { + _controller.pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId.toString(), + widget.courseeid, + e.value.isFinished!), + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + quizPage( + judulQuiz: e.value.title + .toString(), + lessonId: e + .value.lessonId + .toString(), + ), + )) + } + : (e.value.attachment + .toString() + .contains('.pdf')) + ? { + print('ini pdf'), + (_isyt == true) + ? { + _controller.pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget.courseeid, + e.value.isFinished!), + // print(e.value.lessonId.toString()), + // print(widget.courseeid), + // print(e.value.isFinished.toString()), + // _chewieController.pause(), + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + pdfReader( + link: e.value + .attachment, + title: e.value.title + .toString(), + ), + )) + } + : (e.value.attachment + .toString() + .contains( + '.xlsx')) || + (e.value.attachment + .toString() + .contains('.xls')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget.courseeid, + e.value + .isFinished!), + openFileFromUrl(context, + e.value.attachment) + } + : (e.value.attachment + .toString() + .contains( + '.pptx')) || + (e.value.attachment + .toString() + .contains( + '.ppt')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value.attachment + .toString() + .contains( + '.rar')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value + .lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value + .attachment + .toString() + .contains( + '.zip')) + ? { + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment( + e.value + .lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value.attachment.toString().contains('.docx')) + ? { + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment( + e.value.lessonId.toString(), + widget.courseeid, + e.value.isFinished!), + openFileFromUrl( + context, + e.value.attachment) + } + : ((e.value.attachment.toString().contains('.jpg') || e.value.attachment.toString().contains('.jpeg') || e.value.attachment.toString().contains('.png'))) + ? { + (e.value.attachment == null) + ? { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Dokumen Belum Tersedia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )) + } + : { + print('ini image'), + // _controller.pause(), + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment(e.value.lessonId.toString(), widget.courseeid, e.value.isFinished!), + + loadImage(e.value.attachment) + }, + } + : (e.value.attachment.toString().contains('.txt')) + ? { + (e.value.attachment == null) + ? { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Dokumen Belum Tersedia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )) + } + : { + print('ini text'), + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment(e.value.lessonId.toString(), widget.courseeid, e.value.isFinished!), + openFileFromUrl(context, e.value.attachment) + }, + } + : ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Bukan video url ', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + : () { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Mohon tunggu player belum siap ', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + cekbox( + context, + e.value.isFinished ?? 0, + e.value.isSkip ?? '0', + e.value.lessonId ?? '', + lessonMapIdoper, + dataLessonOper, + e.value.lessonType.toString(), + e.value.videoType.toString(), + widget.courseeid), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + // color: Colors.amber, + width: getProportionateScreenWidth(190), + child: Text( + _parseHtmlString( + e.value.title.toString()), + style: thirdTextStyle.copyWith( + fontSize: 12, + color: Theme.of(context) + .colorScheme + .onBackground, + // letterSpacing: 0.5, + ), + ), + ), + Text( + (e.value.lessonType == 'video') + ? 'Video - ${formatDuration(e.value.duration.toString())}' + : (e.value.lessonType == 'quiz') + ? 'Quiz' + : (e.value.attachment + .toString() + .contains('.pdf')) + ? 'PDFs' + : (e.value.attachment + .toString() + .contains('.pptx')) + ? 'PPT' + : (e.value.attachment + .toString() + .contains('.rar')) + ? 'RAR' + : (e.value.attachment + .toString() + .contains( + '.zip')) + ? 'ZIP' + : (e.value + .attachment + .toString() + .contains( + '.xlsx')) + ? 'Excel' + : (e.value.attachment.toString().contains('.jpg') || + e.value + .attachment + .toString() + .contains( + '.jpeg') || + e.value + .attachment + .toString() + .contains( + '.png')) + ? 'Image' + : (e.value + .attachment + .toString() + .contains('.docx')) + ? 'Document' + : (e.value.attachment.toString().contains('.txt')) + ? 'Text' + : 'Terjadi Kesalahan', + style: TextStyle( + fontSize: 10, + color: Theme.of(context) + .colorScheme + .onBackground, + letterSpacing: 0.5, + fontFamily: 'Noto Sans', + ), + ), + ], + ), + Spacer(), + if (e.value.lessonType == 'video') + Image.asset( + 'assets/images/play_button_new.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(3), + ) + else if (e.value.lessonType == 'quiz') + Image.asset( + 'assets/icons/lms/ListNumbers.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.pdf')) + Image.asset( + 'assets/icons/lms/FilePdf.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.rar')) + Image.asset( + 'assets/icons/lms/FileArchive.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.zip')) + Image.asset( + 'assets/icons/lms/FileZip.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.pptx')) + Image.asset( + 'assets/icons/lms/FilePpt.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.xlsx')) + Image.asset( + 'assets/icons/lms/FileXls.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.jpg') || + e.value.attachment + .toString() + .contains('.jpeg') || + e.value.attachment + .toString() + .contains('.png')) + Image.asset( + 'assets/icons/lms/FileImage.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.txt')) + Image.asset( + 'assets/icons/lms/FileText.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if ((e.value.attachment + .toString() + .contains('.docx'))) + Image.asset( + 'assets/icons/lms/FileDoc.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else + SizedBox(), + ], + ), + ], + ), + ), + ), + ) + .toList(), + ), + )); + } + + // Widget TabBar dan TabBarView di bawah video player + Widget tabbarbawah( + BuildContext context, + List lesonOper, + Iterable<Datum> sectionOper, + Map<String?, dynamic> lessonMapIdoper, + List<DataLesson> dataLessonOper) { + return DefaultTabController( + length: 4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + spreadRadius: 0.05, + blurRadius: 5, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(10), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: Text( + '${widget.judul}', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14), + fontWeight: semiBold), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + vertical: getProportionateScreenHeight(8)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('${widget.instruktur}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: + Color(0xff727272), + )), + GestureDetector( + onTap: () { + _controller.pause(); + _videoPlayerController.pause(); + + Navigator.of(context, rootNavigator: true).push( + MaterialPageRoute( + builder: (context) => Sertif( + totalProgress: totalProgress, + idCourse: int.parse(widget.courseeid), + ), + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + "assets/icons/certificate.svg", + color: Theme.of(context).brightness == Brightness.light + ? primaryColorligtmode + : primaryColor, + width: getProportionateScreenWidth(13), + ), + SizedBox( + width: 5, + ), + Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + color: Theme.of(context).brightness == Brightness.light + ? primaryColorligtmode + : primaryColor), + ) + ], + ), + ) + ], + ), + ), + Consumer<myCourseProvider.MyCourseProvider>( + builder: (context, state, _) { + var progres = state.result!.data[0] + .map((e) => e) + .where( + (element) => + element.courseId == + lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + ) + .toList(); + print(progres[0]); + double progressWidth = (SizeConfig.screenWidth - + getProportionateScreenWidth(20)) * + int.parse(progres[0].totalProgress.toString()) / + 100; + print("ini progres width" + progressWidth.toString()); + + totalProgress = progres[0].totalProgress!; + return (progressWidth == 0.0) + ? Padding( + padding: EdgeInsets.only( + left: progressWidth, + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))) + : (progressWidth > 390) + ? Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10), + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: + getProportionateScreenWidth(35), + height: + getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: + BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + // '${progressWidth}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))), + ) + : Padding( + padding: EdgeInsets.only( + left: progressWidth - + getProportionateScreenWidth(5), + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: + BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))); + }, + ), + SizedBox( + height: getProportionateScreenWidth(5), + ), + Consumer<myCourseProvider.MyCourseProvider>( + builder: (context, state, _) { + var progres = state.result!.data[0] + .map((e) => e) + .where( + (element) => + element.courseId == + lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + ) + .toList(); + // print(progres[0]); + double progressWidth = (SizeConfig.screenWidth - + getProportionateScreenWidth(20)) * + int.parse(progres[0].totalProgress.toString()) / + 100; + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Stack( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenWidth(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey[300], + ), + ), + Container( + width: progressWidth, + height: getProportionateScreenWidth(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor, + ), + ), + ], + ), + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(24)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: TabBar( + isScrollable: true, + padding: EdgeInsets.zero, + unselectedLabelColor: + Theme.of(context).colorScheme.onBackground, + labelColor: primaryColor, + indicatorColor: primaryColor, + controller: _tabController, + labelPadding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + tabAlignment: TabAlignment.start, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + tabs: [ + _getTab(0, 'Konten'), + _getTab(1, 'Detail'), + _getTab(2, 'Q & A'), + _getTab(3, 'Pengumuman'), + ], + ), + ), + ], + ), + ), + + // Container( + // width: double.infinity, + // height: 1, + // decoration: BoxDecoration(boxShadow: [ + // BoxShadow( + // blurRadius: 5, spreadRadius: 0.1, offset: Offset(0, 10)) + // ]), + // ), + SizedBox( + height: getProportionateScreenHeight(295), + child: TabBarView( + // physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: [ + Column( + children: [ + SizedBox( + height: getProportionateScreenHeight(15), + ), + SizedBox( + height: getProportionateScreenHeight(5), + ), + Expanded( + child: ListView.builder( + itemCount: sectionOper.length, + itemBuilder: (context, index) { + final tileKey = GlobalKey(); + _tileKeys.add(tileKey); + var e = sectionOper.toList()[index]; + return Theme( + data: ThemeData.dark().copyWith( + colorScheme: + ColorScheme.dark(primary: secondaryColor), + dividerColor: Colors.transparent, + ), + child: Container( + margin: EdgeInsets.only(bottom: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10)), + child: Column( + children: [ + tileKursus( + context, + tileKey, + index, + lesonOper, + sectionOper, + e, + lessonMapIdoper, + dataLessonOper) + ], + ), + ), + ); + }), + ), + ], + ), + DetailPlayCourse(), + QuestAndAnswer( + id: lesonOper.map((e) => e.courseId ?? '').toList().first, + idLesson: + lesonOper.map((e) => e.lessonId ?? '').toList().first, + ), + Announcement( + id: lesonOper.map((e) => e.courseId ?? '').toList().first, + lesonOper: lesonOper, + sectionOper: sectionOper, + lessonMapIdoper: lessonMapIdoper, + dataLessonOper: dataLessonOper, + ), + ], + ), + ) + ], + ), + ); + } + + // Widget utama yang membangun halaman play course + @override + Widget build(BuildContext context) { + // bool isDarkMode = brightnessValue == Brightness.dark; + + resetExpansionTileKeysAndSelectedIndex(); + + final selectedTitleProvider = Provider.of<SelectedTitleProvider>(context); + final playVideoCourseProvider = + Provider.of<PlayVideoCourseProvider>(context, listen: false); + final dataProgress = Provider.of<myCourseProvider.MyCourseProvider>(context); + + // Tambahkan variabel _listVideoUrl untuk menyimpan daftar URL video + List<String> _listVideoUrl = dataLesson + .map((e) => YoutubePlayer.convertUrlToId(e.videoUrl ?? '') ?? '') + .where((element) => element.isNotEmpty) + .toList(); + + return Consumer<LessonCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.loading) { + return const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ); + } else if (state.state == ResultState.hasData) { + var leson = state.result!.data[0]; + var section = state.sectionResult!.data[0].values; + + section.forEach((element) => element.dataLesson! + .forEach((element) => dataLesson.add(element))); + + List<String> _listVideoUrl = dataLesson + .map((e) => YoutubePlayer.convertUrlToId(e.videoUrl ?? '') ?? '') + .where((element) => element.isNotEmpty) + .toList(); + + Map<String?, dynamic> lessonMapId = Map.fromIterable(dataLesson, + key: (e) => YoutubePlayer.convertUrlToId(e.videoUrl ?? ''), + value: (e) => e.lessonId) + ..removeWhere((key, value) => key == null || value == null); + + _isPlayerReady = true; + + + return YoutubePlayerBuilder( + player: YoutubePlayer( + controller: _controller, + width: SizeConfig.screenWidth, + aspectRatio: 16 / 9, + showVideoProgressIndicator: true, + progressColors: const ProgressBarColors( + handleColor: primaryColor, + bufferedColor: Colors.white24, + playedColor: primaryColor, + backgroundColor: Colors.white24, + ), + topActions: [ + GestureDetector( + onTap: () { + // Aksi tombol tutup untuk player + }, + child: Icon(Icons.expand_more), + ), + Spacer(), + Switch( + inactiveThumbImage: AssetImage('assets/images/switch.png'), + activeColor: tenthColor, + value: state.switchbutton, + onChanged: (s) { + state.autoplay(); + }, + ), + Padding( + padding: EdgeInsets.only(left: 5), + child: GestureDetector( + onTap: enablePip, + child: Icon(Icons.picture_in_picture), + ), + ), + ], + onEnded: (state.switchbutton) + ? (data) async { + print('Video berakhir, memuat video berikutnya...'); + // await state.updateLessonCourse( + // lessonMapId[state.url], + // ); + setState(() { + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == lessonMapId[state.url]) + .first + .isFinished = 1; + }); + + Provider.of<myCourseProvider.MyCourseProvider>( + context, + listen: false, + ).getMyCourse().then((value) { + final nextVideoId = _listVideoUrl[ + (_listVideoUrl.indexOf(data.videoId) + 1) % + _listVideoUrl.length]; + _controller.load(nextVideoId); + state.indexUri(nextVideoId); + }); + } + : (data) async { + // await state.updateLessonCourse( + // lessonMapId[state.url], + // ); + Provider.of<myCourseProvider.MyCourseProvider>( + context, + listen: false, + ).getMyCourse(); + setState(() { + _gabisaskip = false; + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == lessonMapId[state.url]) + .first + .isFinished = 1; + }); + }, + onReady: () { + if (_listVideoUrl.isNotEmpty) { + _controller.load(_listVideoUrl.first); + _isvideoClicked = true; + playVideoCourseProvider.uri = _listVideoUrl.first; + } + }, + ), + + builder: (context, player) { + return Container( + color: Colors.black, + child: SafeArea( + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + key: globalScaffoldKey, + body: Column( + children: [ + if (_isvideoClicked && _isyt) + PiPSwitcher( + childWhenEnabled: player, + childWhenDisabled: player, + ), + if (!_isvideoClicked) + AspectRatio( + aspectRatio: 16.0 / 9.0, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget.thumbnail), + ), + ), + ), + ), + if (_isvideoClicked && !_isyt && _chewieController != null) + PiPSwitcher( + childWhenEnabled: AspectRatio( + aspectRatio: 16 / 9, + child: Chewie(controller: _chewieController), + ), + childWhenDisabled: AspectRatio( + aspectRatio: 16 / 9, + child: Chewie(controller: _chewieController), + ), + ), + Expanded( + child: SingleChildScrollView( + child: Column( + children: [ + tabbarbawah( + context, leson, section, lessonMapId, dataLesson), + ], + ), + ), + ), + ], + ), + ), + ), + ); + }, + ); + } else if (state.state == ResultState.error) { + Future.delayed(Duration.zero, () { + Navigator.pop(context); + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus tidak memiliki materi pembelajaran", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context); + }); + return Center( + child: Column( + children: [ + Text( + '', + style: thirdTextStyle, + ), + ], + ), + ); + } else { + return Center( + child: Text( + 'Terjadi kesalahan', + style: thirdTextStyle, + ), + ); + } + }, + ); +} + + + + +} diff --git a/lib/screens/course/play_course_page_old.dart b/lib/screens/course/play_course_page_old.dart new file mode 100644 index 0000000..3baa7b7 --- /dev/null +++ b/lib/screens/course/play_course_page_old.dart @@ -0,0 +1,2022 @@ +// import 'dart:html'; + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:easy_image_viewer/easy_image_viewer.dart'; +import 'package:floating/floating.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/main.dart'; +import 'package:http/http.dart' as http; +import 'package:flutter_text_viewer/flutter_text_viewer.dart'; + +import 'package:initial_folder/models/section_model.dart'; +import 'package:initial_folder/providers/certificate_provider.dart' + as certifProvider; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; +import 'package:initial_folder/providers/play_video_course_provider.dart'; +import 'package:initial_folder/providers/selected_title_provider.dart'; +import 'package:initial_folder/screens/course/component/announcement.dart'; + +import 'package:initial_folder/screens/course/component/detail_play_course.dart'; +import 'package:initial_folder/screens/course/component/pdfReader.dart'; +import 'package:initial_folder/screens/course/component/quest_and_answer.dart'; +import 'package:initial_folder/screens/course/component/txtReader.dart'; +import 'package:initial_folder/screens/course/quiz_page.dart'; +import 'package:initial_folder/screens/course/sertif.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_expansion_tile.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:chewie/chewie.dart'; +import 'package:video_player/video_player.dart'; +import './component/expansion_tile_copy.dart'; + +class PlayCourseOld extends StatefulWidget { + const PlayCourseOld( + {Key? key, + required this.instruktur, + required this.judul, + required this.thumbnail, + required this.courseeid, + this.isQna}) + : super(key: key); + final String judul, instruktur, thumbnail, courseeid; + final bool? isQna; + + @override + State<PlayCourseOld> createState() => _PlayCoursePageState(); +} + +class _PlayCoursePageState extends State<PlayCourseOld> + with SingleTickerProviderStateMixin, WidgetsBindingObserver { + late TabController _tabController; + LessonCourseService lessonCourseService = LessonCourseService(); + + late YoutubePlayerController _controller; + + late PlayerState _playerState; + late YoutubeMetaData _videoMetaData; + bool _isPlayerReady = false; + int totalProgress = 0; + bool _isvideoClicked = false; + bool _isyt = true; + String linkhtml5 = ''; + bool _gabisaskip = false; + List<DataLesson> dataLesson = []; + final floating = Floating(); + var _selectedIndex = 0; + int? _expandedTileIndex; + final _tileKeys = []; + + String formatDuration(String duration) { + if (duration == '') { + String formattedDuration = ''; + + print('masuk sini'); + formattedDuration += ''; + return formattedDuration; + } + List<String> parts = duration.split(':'); + int hours = int.parse(parts[0]); + int minutes = int.parse(parts[1]); + int seconds = int.parse(parts[2]); + + String formattedDuration = ''; + + if (hours != 0) { + formattedDuration += '${hours}j'; + } + + if (minutes != 0) { + formattedDuration += '${int.parse(parts[1])}m'; + } + + formattedDuration += '${int.parse(parts[2])}d'; + + return formattedDuration; + } + + String courseID_local_end = ''; + String lessonID_local_end = ''; + late ChewieController _chewieController; + VideoPlayerController _videoPlayerController = VideoPlayerController.network( + 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); + + @override + void initState() { + super.initState(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + WidgetsBinding.instance.addObserver(this); + _tabController = TabController( + vsync: this, length: 4, initialIndex: (widget.isQna == true) ? 2 : 0); + _controller = YoutubePlayerController( + //todo + initialVideoId: YoutubePlayer.convertUrlToId('') ?? '', + flags: const YoutubePlayerFlags( + mute: false, + disableDragSeek: false, + loop: false, + isLive: false, + forceHD: false, + enableCaption: false, + autoPlay: true, + hideThumbnail: true, + ), + ); + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoPlay: true, + looping: false, + aspectRatio: 16 / 9, + // Other ChewieController configurations... + ); + } + + void _initializeytController(String url) async { + setState(() { + _controller = YoutubePlayerController( + //todo + initialVideoId: YoutubePlayer.convertUrlToId(url).toString(), + flags: const YoutubePlayerFlags( + mute: false, + disableDragSeek: false, + loop: false, + isLive: false, + forceHD: false, + enableCaption: false, + autoPlay: true, + hideThumbnail: true, + ), + ); + }); + } + + void _initializeChewieController(String videoUrl) async { + _videoPlayerController = VideoPlayerController.network(videoUrl); + // await _videoPlayerController.initialize(); + + setState(() { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoPlay: true, + looping: false, + aspectRatio: 16 / 9, + additionalOptions: (context) { + return <OptionItem>[ + OptionItem( + onTap: () { + enablePip(); + Navigator.pop(context); + }, + iconData: Icons.picture_in_picture, + title: 'Aktifkan mode PiP', + ), + ]; + }, + // customControls: CustomChewieControls() + // Other ChewieController configurations... + ); + //todo + _videoPlayerController.addListener(() async { + if (_videoPlayerController.value.position == + _videoPlayerController.value.duration) { + print('video Ended'); + print(widget.courseeid); + print(lessonID_local_end); + LessonCourseService() + .updateLessonCourse(lessonID_local_end) + .then((value) => { + if (value == 201) {print('berhasil')} + }); + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + _gabisaskip = false; + + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonID_local_end) + .first + .isFinished = 1; + }); + + // print('g masuk'); + } + }); + }); + } + + @override + void dispose() { + _chewieController.dispose(); + _videoPlayerController.dispose(); + WidgetsBinding.instance.removeObserver(this); + floating.dispose(); + + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState lifecycleState) { + super.didChangeAppLifecycleState(lifecycleState); + // if (lifecycleState == AppLifecycleState.inactive) { + // floating.enable(Rational.landscape()); + // } + if (lifecycleState == AppLifecycleState.resumed) { + // Aplikasi kembali dari background (termasuk dari mode PiP) + // Menampilkan status bar kembali + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + } + } + + Future<void> enablePip() async { + final status = await floating.enable(Rational.landscape()); + debugPrint('Pipenable? $status'); + } + + @override + void deactivate() { + // Pauses video while navigating to next page. + _controller.pause(); + // _chewieController.pause(); + super.deactivate(); + } + + // void listener() { + // if (_isPlayerReady && mounted && !_controller.value.isFullScreen) { + // setState(() { + // _playerState = _controller.value.playerState; + // _videoMetaData = _controller.metadata; + // }); + // } + // if (_playerState == PlayerState.ended) { + // print('Video telah selesai diputar'); + // } + // } + + void loadImage(String link) async { + final imageProvider = + Image.network("https://api.vokasia.id/uploads/lesson_files/" + link) + .image; + showImageViewer(context, imageProvider, doubleTapZoomable: true, + onViewerDismissed: () { + print("dismissed"); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + }); + } + + Future<void> openFileFromUrl(BuildContext context, String fileUrl) async { + try { + print(fileUrl); + final response = await http.get( + Uri.parse('https://api.vokasia.id/uploads/lesson_files/$fileUrl')); + if (response.statusCode == 200) { + final fileBytes = response.bodyBytes; + final fileName = fileUrl.split('/').last; + final fileType = 'txt'; // Sesuaikan dengan tipe file yang ingin dibuka + + final tempDir = await getTemporaryDirectory(); + final tempFilePath = '${tempDir.path}/$fileName'; + + await File(tempFilePath).writeAsBytes(fileBytes); + print(tempFilePath); + OpenFile.open(tempFilePath); + // Buka file menggunakan aplikasi default pada perangkat + // Contoh: OpenFile.open(tempFilePath); + } else { + print('Failed to download file: ${response.statusCode}'); + } + } catch (e) { + print('Error opening file from URL: $e'); + } + } + + void resetExpansionTileKeysAndSelectedIndex() { + _tileKeys.clear(); + _selectedIndex = 0; + } + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + String _parseHtmlString(String htmlText) { + RegExp exp = RegExp(r"<[^>]*>| |&|"", + multiLine: true, caseSensitive: true); + return htmlText.replaceAll(exp, ''); + } + + resetExpansionTileKeysAndSelectedIndex(); + final selectedTitleProvider = Provider.of<SelectedTitleProvider>(context); + PlayVideoCourseProvider playVideoCourseProvider = + Provider.of<PlayVideoCourseProvider>(context); + var dataProgress = Provider.of<myCourseProvider.MyCourseProvider>(context); + // var dataProgress22 = Provider.of<myCourseProvider.MyCourseProvider>(context).courseService; + + _getTab(index, child) { + return Tab( + height: 30, + iconMargin: EdgeInsets.zero, + child: Container( + child: Center( + child: Text( + child, + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.2, + letterSpacing: 0.4, + ), + ), + ), + ), + ); + } + + _ceklisattachment( + String lessonId, + String idCourse, + int isFinished, + ) async { + print('ini lesson id ->' + lessonId); + print('ini course id ->' + idCourse); + await lessonCourseService.updateLessonCourse(lessonId).then((value) => { + print('ini value dari sana' + value.toString()), + if (value == '201') + setState(() { + { + isFinished = 1; + } + }) + }); + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonId) + .first + .isFinished = 1; + }); + } + + // Widget cekbox( + // int isFinished, String isSkipped, String courseId, String lessonId) { + // return Checkbox( + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + // activeColor: primaryColor, + // value: (isFinished == 1) ? true : false, + // onChanged: (value) { + // if (isSkipped == '1') { + // Provider.of<LessonCourseProvider>(context) + // .updateLessonCourse(courseId, lessonId); + // } + // }); + // } + + Duration getVideoDuration(String duration) { + int hours = 0; + int minutes = 0; + int seconds = 0; + List<String> timeSplit = duration.split(':'); + hours = int.parse(timeSplit[0]); + minutes = int.parse(timeSplit[1]); + seconds = int.parse(timeSplit[2]); + return Duration(hours: hours, minutes: minutes, seconds: seconds - 1); + } + + Widget cekbox( + int isFinished, + String isSkipped, + String lessonId, + Map<String?, dynamic> lessonMapId, + List<DataLesson> dataLesson, + String lessontypeee, + String videotypeee, + String idCourse) { + return Theme( + data: Theme.of(context).copyWith(unselectedWidgetColor: Colors.grey), + child: Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4)), + activeColor: primaryColor, + checkColor: Colors.white, + value: (isFinished == 1) ? true : false, + onChanged: (value) async { + // print(lessontypeee); + var data = dataLesson + .map((e) => e) + .where((lesson) => + lesson.lessonId == + lessonMapId['${playVideoCourseProvider.url}']) + .first; + if (lessontypeee != 'video') { + //todo + if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Anda tidak dapat mencentang pelajaran\nyang tidak dapat diskip', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } else { + print('ini lesson id ->' + lessonId); + print('ini course id ->' + idCourse); + await lessonCourseService + .updateLessonCourse(lessonId) + .then((value) => { + print('ini value dari sana' + value.toString()), + if (value == '201') + setState(() { + { + isFinished = 1; + } + }) + }); + await Provider.of<myCourseProvider.MyCourseProvider>( + context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonId) + .first + .isFinished = 1; + }); + } + } else if (videotypeee == 'html5') { + print('ceklis html5 diklik'); + + if (isSkipped == '1') { + _chewieController.seekTo(getVideoDuration(data.duration!)); + } else if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + } + + if (lessonId == lessonMapId['${playVideoCourseProvider.url}'] && + lessonId != '') { + if (isSkipped == '1') { + print('masuk kesini'); + _controller.seekTo(getVideoDuration(data.duration!)); + } else if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + } + })); + } + + Widget tileKursus( + Key tileKeyoper, + int index, + List lesonOper, + Iterable<Datum> sectionOper, + Datum e, + Map<String?, dynamic> lessonMapIdoper, + List<DataLesson> dataLessonOper) { + return ListTileTheme( + dense: true, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 15, vertical: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + spreadRadius: 0.01, + blurRadius: 1, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: ExpansionTileCopy( + key: tileKeyoper, + initiallyExpanded: index == _expandedTileIndex, + onExpansionChanged: (value) { + // If tile is expanding, then collapse the already expanded tile. + if (value) { + if (index != _selectedIndex) { + _tileKeys[_selectedIndex].currentState!.closeExpansion(); + } + _selectedIndex = index; + } + }, + title: Text( + 'Bab ${index + 1}', + style: thirdTextStyle.copyWith( + fontWeight: bold, + letterSpacing: 0.5, + color: Theme.of(context).colorScheme.onBackground, + fontSize: getProportionateScreenWidth(12)), + ), + subtitle: Html( + shrinkWrap: true, + data: e.sectionTitle, + style: { + "body": Style( + margin: Margins.zero, + padding: HtmlPaddings.zero, + fontSize: FontSize(getProportionateScreenWidth(12)), + fontWeight: semiBold, + letterSpacing: 0.5, + fontFamily: 'Poppins', + color: Theme.of(context).colorScheme.onBackground), + }, + ), + children: e.dataLesson! + .asMap() + .entries + .map( + (e) => Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(20), + bottom: getProportionateScreenHeight(5)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10)), + child: GestureDetector( + onTap: _isPlayerReady + ? (e.value.videoType == 'youtubelink' || + e.value.videoType == 'YouTube') + ? () { + print('masuk sini'); + setState(() { + _isvideoClicked = true; + _isPlayerReady = true; + }); + selectedTitleProvider.selectedTitle = + e.value.title; + Announcement announcement = Announcement( + id: lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + lesonOper: lesonOper, + sectionOper: sectionOper, + lessonMapIdoper: lessonMapIdoper, + dataLessonOper: dataLessonOper, + ); + announcement.showSummary(e.value.title); + print(_gabisaskip); + if (_gabisaskip == true) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + )); + } else { + if (e.value.isSkip == '1') { + setState(() { + _gabisaskip = false; + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url); + _isyt = true; + } else { + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider + .url) ?? + '', + ); + _isyt = true; + } + _isyt = true; + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 1) { + setState(() { + _gabisaskip = false; + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url); + _isyt = true; + } else { + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider + .url) ?? + '', + ); + _isyt = true; + } + _isyt = true; + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 0) { + //todo + setState(() { + _gabisaskip = true; + + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url); + _isyt = true; + } else { + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider + .url) ?? + '', + ); + _isyt = true; + } + _isyt = true; + }); + } + } + } + : (e.value.videoType == 'html5') + ? () { + setState(() { + _isvideoClicked = true; + _isPlayerReady = true; + }); + print(_gabisaskip); + if (_gabisaskip == true) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + )); + } else { + if (e.value.isSkip == '1') { + setState(() { + _gabisaskip = false; + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 1) { + setState(() { + _gabisaskip = false; + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 0) { + //todo + setState(() { + _gabisaskip = true; + + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } + } + } + : () { + (e.value.lessonType == 'quiz') + ? { + print('ini quiz'), + (_isyt == true) + ? { + _controller.pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId.toString(), + widget.courseeid, + e.value.isFinished!), + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + quizPage( + judulQuiz: e.value.title + .toString(), + lessonId: e + .value.lessonId + .toString(), + ), + )) + } + : (e.value.attachment + .toString() + .contains('.pdf')) + ? { + print('ini pdf'), + (_isyt == true) + ? { + _controller.pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget.courseeid, + e.value.isFinished!), + // print(e.value.lessonId.toString()), + // print(widget.courseeid), + // print(e.value.isFinished.toString()), + // _chewieController.pause(), + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + pdfReader( + link: e.value + .attachment, + title: e.value.title + .toString(), + ), + )) + } + : (e.value.attachment + .toString() + .contains( + '.xlsx')) || + (e.value.attachment + .toString() + .contains('.xls')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget.courseeid, + e.value + .isFinished!), + openFileFromUrl(context, + e.value.attachment) + } + : (e.value.attachment + .toString() + .contains( + '.pptx')) || + (e.value.attachment + .toString() + .contains( + '.ppt')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value.attachment + .toString() + .contains( + '.rar')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value + .lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value + .attachment + .toString() + .contains( + '.zip')) + ? { + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment( + e.value + .lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value.attachment.toString().contains('.docx')) + ? { + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment( + e.value.lessonId.toString(), + widget.courseeid, + e.value.isFinished!), + openFileFromUrl( + context, + e.value.attachment) + } + : ((e.value.attachment.toString().contains('.jpg') || e.value.attachment.toString().contains('.jpeg') || e.value.attachment.toString().contains('.png'))) + ? { + (e.value.attachment == null) + ? { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Dokumen Belum Tersedia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )) + } + : { + print('ini image'), + // _controller.pause(), + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment(e.value.lessonId.toString(), widget.courseeid, e.value.isFinished!), + + loadImage(e.value.attachment) + }, + } + : (e.value.attachment.toString().contains('.txt')) + ? { + (e.value.attachment == null) + ? { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Dokumen Belum Tersedia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )) + } + : { + print('ini text'), + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment(e.value.lessonId.toString(), widget.courseeid, e.value.isFinished!), + openFileFromUrl(context, e.value.attachment) + }, + } + : ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Bukan video url ', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + : () { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Mohon tunggu player belum siap ', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + cekbox( + e.value.isFinished ?? 0, + e.value.isSkip ?? '0', + e.value.lessonId ?? '', + lessonMapIdoper, + dataLessonOper, + e.value.lessonType.toString(), + e.value.videoType.toString(), + widget.courseeid), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + // color: Colors.amber, + width: getProportionateScreenWidth(190), + child: Text( + _parseHtmlString( + e.value.title.toString()), + style: thirdTextStyle.copyWith( + fontSize: 12, + color: Theme.of(context) + .colorScheme + .onBackground, + // letterSpacing: 0.5, + ), + ), + ), + Text( + (e.value.lessonType == 'video') + ? 'Video - ${formatDuration(e.value.duration.toString())}' + : (e.value.lessonType == 'quiz') + ? 'Quiz' + : (e.value.attachment + .toString() + .contains('.pdf')) + ? 'PDFs' + : (e.value.attachment + .toString() + .contains('.pptx')) + ? 'PPT' + : (e.value.attachment + .toString() + .contains('.rar')) + ? 'RAR' + : (e.value.attachment + .toString() + .contains( + '.zip')) + ? 'ZIP' + : (e.value + .attachment + .toString() + .contains( + '.xlsx')) + ? 'Excel' + : (e.value.attachment.toString().contains('.jpg') || + e.value + .attachment + .toString() + .contains( + '.jpeg') || + e.value + .attachment + .toString() + .contains( + '.png')) + ? 'Image' + : (e.value + .attachment + .toString() + .contains('.docx')) + ? 'Document' + : (e.value.attachment.toString().contains('.txt')) + ? 'Text' + : 'Terjadi Kesalahan', + style: TextStyle( + fontSize: 10, + color: Theme.of(context) + .colorScheme + .onBackground, + letterSpacing: 0.5, + fontFamily: 'Noto Sans', + ), + ), + ], + ), + Spacer(), + if (e.value.lessonType == 'video') + Image.asset( + 'assets/images/play_button_new.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(3), + ) + else if (e.value.lessonType == 'quiz') + Image.asset( + 'assets/icons/lms/ListNumbers.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.pdf')) + Image.asset( + 'assets/icons/lms/FilePdf.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.rar')) + Image.asset( + 'assets/icons/lms/FileArchive.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.zip')) + Image.asset( + 'assets/icons/lms/FileZip.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.pptx')) + Image.asset( + 'assets/icons/lms/FilePpt.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.xlsx')) + Image.asset( + 'assets/icons/lms/FileXls.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.jpg') || + e.value.attachment + .toString() + .contains('.jpeg') || + e.value.attachment + .toString() + .contains('.png')) + Image.asset( + 'assets/icons/lms/FileImage.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.txt')) + Image.asset( + 'assets/icons/lms/FileText.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if ((e.value.attachment + .toString() + .contains('.docx'))) + Image.asset( + 'assets/icons/lms/FileDoc.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else + SizedBox(), + ], + ), + ], + ), + ), + ), + ) + .toList(), + ), + )); + } + + Widget tabbarbawah( + BuildContext context, + List lesonOper, + Iterable<Datum> sectionOper, + Map<String?, dynamic> lessonMapIdoper, + List<DataLesson> dataLessonOper) { + return DefaultTabController( + length: 4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + spreadRadius: 0.05, + blurRadius: 5, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(10), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: Text( + '${widget.judul}', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14), + fontWeight: semiBold), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + vertical: getProportionateScreenHeight(8)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('${widget.instruktur}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: isDarkMode + ? Color(0xff727272) + : Color(0xff727272), + )), + GestureDetector( + onTap: () { + _controller.pause(); + _videoPlayerController.pause(); + + Navigator.of(context, rootNavigator: true).push( + MaterialPageRoute( + builder: (context) => Sertif( + totalProgress: totalProgress, + idCourse: int.parse(widget.courseeid), + ), + ), + ); + }, + child: Row( + children: [ + Image.asset( + 'assets/images/certificate_icon.png', + scale: getProportionateScreenWidth(1.3), + ), + SizedBox( + width: 5, + ), + Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + color: primaryColor), + ) + ], + ), + ) + ], + ), + ), + Consumer<myCourseProvider.MyCourseProvider>( + builder: (context, state, _) { + var progres = state.result!.data[0] + .map((e) => e) + .where( + (element) => + element.courseId == + lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + ) + .toList(); + print(progres[0]); + double progressWidth = (SizeConfig.screenWidth - + getProportionateScreenWidth(20)) * + int.parse(progres[0].totalProgress.toString()) / + 100; + print("ini progres width" + progressWidth.toString()); + + totalProgress = progres[0].totalProgress!; + return (progressWidth == 0.0) + ? Padding( + padding: EdgeInsets.only( + left: progressWidth, + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))) + : (progressWidth > 390) + ? Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10), + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: + getProportionateScreenWidth(35), + height: + getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: + BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + // '${progressWidth}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))), + ) + : Padding( + padding: EdgeInsets.only( + left: progressWidth - + getProportionateScreenWidth(5), + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: + BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))); + }, + ), + SizedBox( + height: getProportionateScreenWidth(5), + ), + Consumer<myCourseProvider.MyCourseProvider>( + builder: (context, state, _) { + var progres = state.result!.data[0] + .map((e) => e) + .where( + (element) => + element.courseId == + lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + ) + .toList(); + // print(progres[0]); + double progressWidth = (SizeConfig.screenWidth - + getProportionateScreenWidth(20)) * + int.parse(progres[0].totalProgress.toString()) / + 100; + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Stack( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenWidth(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey[300], + ), + ), + Container( + width: progressWidth, + height: getProportionateScreenWidth(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor, + ), + ), + ], + ), + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(24)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: TabBar( + isScrollable: true, + padding: EdgeInsets.zero, + unselectedLabelColor: + Theme.of(context).colorScheme.onBackground, + labelColor: primaryColor, + indicatorColor: primaryColor, + controller: _tabController, + labelPadding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + tabAlignment: TabAlignment.start, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + tabs: [ + _getTab(0, 'Konten'), + _getTab(1, 'Detail'), + _getTab(2, 'Q & A'), + _getTab(3, 'Pengumuman'), + ], + ), + ), + ], + ), + ), + // Container( + // width: double.infinity, + // height: 1, + // decoration: BoxDecoration(boxShadow: [ + // BoxShadow( + // blurRadius: 5, spreadRadius: 0.1, offset: Offset(0, 10)) + // ]), + // ), + Expanded( + child: TabBarView( + // physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: [ + Column( + children: [ + SizedBox( + height: getProportionateScreenHeight(15), + ), + SizedBox( + height: getProportionateScreenHeight(5), + ), + Expanded( + child: ListView.builder( + itemCount: sectionOper.length, + itemBuilder: (context, index) { + final tileKey = GlobalKey(); + _tileKeys.add(tileKey); + var e = sectionOper.toList()[index]; + return Theme( + data: ThemeData.dark().copyWith( + colorScheme: + ColorScheme.dark(primary: secondaryColor), + dividerColor: Colors.transparent, + ), + child: Container( + margin: EdgeInsets.only(bottom: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10)), + child: Column( + children: [ + tileKursus( + tileKey, + index, + lesonOper, + sectionOper, + e, + lessonMapIdoper, + dataLessonOper) + ], + ), + ), + ); + }), + ), + ], + ), + DetailPlayCourse(), + QuestAndAnswer( + id: lesonOper.map((e) => e.courseId ?? '').toList().first, + idLesson: + lesonOper.map((e) => e.lessonId ?? '').toList().first, + ), + Announcement( + id: lesonOper.map((e) => e.courseId ?? '').toList().first, + lesonOper: lesonOper, + sectionOper: sectionOper, + lessonMapIdoper: lessonMapIdoper, + dataLessonOper: dataLessonOper, + ), + ], + ), + ) + ], + ), + ); + } + + return Container( + color: Colors.black, + child: SafeArea( + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + key: globalScaffoldKey, + body: Consumer<LessonCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.loading) { + return const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ); + } else if (state.state == ResultState.hasData) { + var leson = state.result!.data[0]; + var section = state.sectionResult!.data[0].values; + + section.forEach((element) => element.dataLesson! + .forEach((element) => dataLesson.add(element))); + + List<String> _listVideoUrl = dataLesson + .map((e) => + YoutubePlayer.convertUrlToId(e.videoUrl ?? '') ?? '') + .where((element) => element.isNotEmpty) + .toList(); + Map<String?, dynamic> lessonMapId = Map.fromIterable(dataLesson, + key: (e) => YoutubePlayer.convertUrlToId(e.videoUrl ?? ''), + value: (e) => e.lessonId) + ..removeWhere((key, value) => key == null || value == null); + _isPlayerReady = true; + return Column( + children: [ + if (_isvideoClicked == false) + AspectRatio( + aspectRatio: 16 / 9, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget.thumbnail), + ), + ), + ), + ) + else if (_isvideoClicked == true && _isyt == true) + PiPSwitcher( + childWhenEnabled: YoutubePlayerBuilder( + player: YoutubePlayer( + controller: _controller, + ), + builder: (p0, p1) { + return YoutubePlayer( + thumbnail: Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget.thumbnail), + ), + ), + ), + showVideoProgressIndicator: true, + progressColors: const ProgressBarColors( + handleColor: primaryColor, + bufferedColor: Colors.white24, + playedColor: primaryColor, + backgroundColor: Colors.white24, + ), + progressIndicatorColor: primaryColor, + topActions: [ + GestureDetector( + onTap: () {}, + child: Icon(Icons.expand_more), + ), + Spacer(), + Switch( + inactiveThumbImage: + AssetImage('assets/images/switch.png'), + activeColor: tenthColor, + value: state.switchbutton, + onChanged: (s) { + state.autoplay(); + }, + ), + ], + onEnded: (state.switchbutton) + ? (data) async { + print('masuk sini'); + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + setState(() async { + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + + await Provider.of< + myCourseProvider.MyCourseProvider>( + context, + listen: false, + ).getMyCourse().then((value) => + _controller.load( + _listVideoUrl[ + (_listVideoUrl.indexOf( + data.videoId) + + 1) % + _listVideoUrl.length], + )); + + playVideoCourseProvider.indexUri( + _listVideoUrl[(_listVideoUrl + .indexOf(data.videoId) + + 1) % + _listVideoUrl.length], + ); + } + : (d) async { + //todo + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + await Provider.of< + myCourseProvider.MyCourseProvider>( + context, + listen: false, + ).getMyCourse(); + setState(() { + print('KELARR DISINIII'); + _gabisaskip = false; + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + // print('g masuk'); + }, + onReady: () { + _controller.load( + YoutubePlayer.convertUrlToId( + _listVideoUrl.first) ?? + '', + ); + + _isPlayerReady = true; + playVideoCourseProvider.uri = + _listVideoUrl.first; + }, + controller: _controller, + ); + }, + ), + childWhenDisabled: YoutubePlayerBuilder( + player: YoutubePlayer( + controller: _controller, + ), + builder: (p0, p1) { + return YoutubePlayer( + thumbnail: Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget.thumbnail), + ), + ), + ), + showVideoProgressIndicator: true, + progressColors: const ProgressBarColors( + handleColor: primaryColor, + bufferedColor: Colors.white24, + playedColor: primaryColor, + backgroundColor: Colors.white24, + ), + progressIndicatorColor: primaryColor, + topActions: [ + GestureDetector( + onTap: () {}, + child: Icon(Icons.expand_more), + ), + Spacer(), + Switch( + inactiveThumbImage: + AssetImage('assets/images/switch.png'), + activeColor: tenthColor, + value: state.switchbutton, + onChanged: (s) { + state.autoplay(); + }, + ), + Padding( + padding: EdgeInsets.only(left: 5), + child: GestureDetector( + onTap: enablePip, + child: Icon(Icons.picture_in_picture), + ), + ) + ], + onEnded: (state.switchbutton) + ? (data) async { + print('masuk sini'); + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + setState(() async { + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + + await Provider.of< + myCourseProvider.MyCourseProvider>( + context, + listen: false, + ).getMyCourse().then((value) => + _controller.load( + _listVideoUrl[ + (_listVideoUrl.indexOf( + data.videoId) + + 1) % + _listVideoUrl.length], + )); + + playVideoCourseProvider.indexUri( + _listVideoUrl[(_listVideoUrl + .indexOf(data.videoId) + + 1) % + _listVideoUrl.length], + ); + } + : (d) async { + //todo + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + await Provider.of< + myCourseProvider.MyCourseProvider>( + context, + listen: false, + ).getMyCourse(); + setState(() { + print('KELARR DISINIII'); + _gabisaskip = false; + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + // print('g masuk'); + }, + onReady: () { + _controller.load( + YoutubePlayer.convertUrlToId( + _listVideoUrl.first) ?? + '', + ); + + _isPlayerReady = true; + playVideoCourseProvider.uri = + _listVideoUrl.first; + }, + controller: _controller, + ); + }, + )) + else if (_isvideoClicked == true && _chewieController == '') + CircularProgressIndicator() + else if (_isvideoClicked == true && _isyt == false) + PiPSwitcher( + childWhenEnabled: AspectRatio( + aspectRatio: 16 / 9, + child: Chewie(controller: _chewieController), + ), + childWhenDisabled: AspectRatio( + aspectRatio: 16 / 9, + child: Chewie(controller: _chewieController), + ), + ), + Flexible( + child: tabbarbawah( + context, leson, section, lessonMapId, dataLesson), + ) + ], + ); + } else if (state.state == ResultState.error) { + Future.delayed(Duration.zero, () { + Navigator.pop(context); + CherryToast.error( + animationDuration: Durations.long1, + title: Text("Kursus tidak memiliki materi pembelajaran", + style: TextStyle( + color: Colors.black, + fontSize: 15, + )), + animationType: AnimationType.fromTop, + ).show(context); + }); + return Center( + child: Column( + children: [ + Text( + '', + style: thirdTextStyle, + ), + ], + )); + } else { + return Center( + child: Text( + 'Terjadi kesalahan', + style: thirdTextStyle, + ), + ); + } + }, + ), + ), + ), + ); + } +} diff --git a/lib/screens/course/play_course_page_old_2.dart b/lib/screens/course/play_course_page_old_2.dart new file mode 100644 index 0000000..d78af5a --- /dev/null +++ b/lib/screens/course/play_course_page_old_2.dart @@ -0,0 +1,2075 @@ +// import 'dart:html'; + +import 'dart:ffi'; +import 'dart:io'; + +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:easy_image_viewer/easy_image_viewer.dart'; +import 'package:floating/floating.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/main.dart'; +import 'package:http/http.dart' as http; +import 'package:flutter_text_viewer/flutter_text_viewer.dart'; + +import 'package:initial_folder/models/section_model.dart'; +import 'package:initial_folder/providers/certificate_provider.dart' + as certifProvider; +import 'package:initial_folder/providers/current_lesson_provider.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; +import 'package:initial_folder/providers/play_video_course_provider.dart'; +import 'package:initial_folder/providers/selected_title_provider.dart'; +import 'package:initial_folder/screens/course/component/announcement.dart'; + +import 'package:initial_folder/screens/course/component/detail_play_course.dart'; +import 'package:initial_folder/screens/course/component/pdfReader.dart'; +import 'package:initial_folder/screens/course/component/quest_and_answer.dart'; +import 'package:initial_folder/screens/course/component/txtReader.dart'; +import 'package:initial_folder/screens/course/quiz_page.dart'; +import 'package:initial_folder/screens/course/sertif.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_expansion_tile.dart'; +import 'package:open_file/open_file.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; +import 'package:chewie/chewie.dart'; +import 'package:video_player/video_player.dart'; +import './component/expansion_tile_copy.dart'; + +class PlayCourse extends StatefulWidget { + const PlayCourse( + {Key? key, + required this.instruktur, + required this.judul, + required this.thumbnail, + required this.courseeid, + this.isQna}) + : super(key: key); + final String judul, instruktur, thumbnail, courseeid; + final bool? isQna; + + @override + State<PlayCourse> createState() => _PlayCoursePageState(); +} + +class _PlayCoursePageState extends State<PlayCourse> + with SingleTickerProviderStateMixin, WidgetsBindingObserver { + late TabController _tabController; + LessonCourseService lessonCourseService = LessonCourseService(); + + late YoutubePlayerController _controller; + + late PlayerState _playerState; + late YoutubeMetaData _videoMetaData; + + Duration _currentTime = const Duration(); + String? _setLocalLEssonId = ""; + int _ytCurrentTime = 0; + bool _isPlayerReady = false; + int totalProgress = 0; + bool _isvideoClicked = false; + bool _isyt = true; + String linkhtml5 = ''; + bool _gabisaskip = false; + List<DataLesson> dataLesson = []; + final floating = Floating(); + var _selectedIndex = 0; + int? _expandedTileIndex; + final _tileKeys = []; + + String formatDuration(String duration) { + if (duration == '') { + String formattedDuration = ''; + + print('masuk sini'); + formattedDuration += ''; + return formattedDuration; + } + List<String> parts = duration.split(':'); + int hours = int.parse(parts[0]); + int minutes = int.parse(parts[1]); + int seconds = int.parse(parts[2]); + + String formattedDuration = ''; + + if (hours != 0) { + formattedDuration += '${hours}j'; + } + + if (minutes != 0) { + formattedDuration += '${int.parse(parts[1])}m'; + } + + formattedDuration += '${int.parse(parts[2])}d'; + + return formattedDuration; + } + + String courseID_local_end = ''; + String lessonID_local_end = ''; + late ChewieController _chewieController; + VideoPlayerController _videoPlayerController = VideoPlayerController.network( + 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'); + + @override + void initState() { + super.initState(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.portraitDown, + ]); + WidgetsBinding.instance.addObserver(this); + _tabController = TabController( + vsync: this, length: 4, initialIndex: (widget.isQna == true) ? 2 : 0); + print( + "----------------------------------------------------------------> setup"); + _controller = YoutubePlayerController( + //todo + initialVideoId: "", + flags: const YoutubePlayerFlags( + mute: false, + disableDragSeek: false, + loop: false, + isLive: false, + forceHD: false, + enableCaption: false, + autoPlay: true, + hideThumbnail: true, + startAt: 0, + ), + )..addListener(() { + setState(() { + _playerState = _controller.value.playerState; + _currentTime = _controller.value.position; + }); + + if (_playerState == PlayerState.paused) { + () async { + await LessonCourseService().updateLessonCourse(_setLocalLEssonId, + progress: _currentTime.inSeconds); + }(); + } + + print( + "INI Current TIME --------------------------------->$_currentTime"); + }); + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoPlay: true, + looping: false, + aspectRatio: 16 / 9, + // Other ChewieController configurations... + ); + } + + void _initializeytController(String url) async { + setState(() { + _controller = YoutubePlayerController( + //todo + initialVideoId: YoutubePlayer.convertUrlToId(url).toString(), + flags: const YoutubePlayerFlags( + mute: false, + disableDragSeek: false, + loop: false, + isLive: false, + forceHD: false, + enableCaption: false, + autoPlay: true, + hideThumbnail: true, + ), + ); + ; + }); + } + + void _initializeChewieController(String videoUrl) async { + _videoPlayerController = VideoPlayerController.network(videoUrl); + // await _videoPlayerController.initialize(); + + setState(() { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoPlay: true, + looping: false, + aspectRatio: 16 / 9, + additionalOptions: (context) { + return <OptionItem>[ + OptionItem( + onTap: () { + enablePip(); + Navigator.pop(context); + }, + iconData: Icons.picture_in_picture, + title: 'Aktifkan mode PiP', + ), + ]; + }, + // customControls: CustomChewieControls() + // Other ChewieController configurations... + ); + //todo + _videoPlayerController.addListener(() async { + if (_videoPlayerController.value.position == + _videoPlayerController.value.duration) { + print('video Ended'); + print(widget.courseeid); + print(lessonID_local_end); + LessonCourseService() + .updateLessonCourse(lessonID_local_end) + .then((value) => { + if (value == 201) {print('berhasil')} + }); + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + _gabisaskip = false; + + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonID_local_end) + .first + .isFinished = 1; + }); + + // print('g masuk'); + } + }); + }); + } + + @override + void dispose() { + _controller.dispose(); + _chewieController.dispose(); + _videoPlayerController.dispose(); + WidgetsBinding.instance.removeObserver(this); + floating.dispose(); + + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState lifecycleState) { + super.didChangeAppLifecycleState(lifecycleState); + // if (lifecycleState == AppLifecycleState.inactive) { + // floating.enable(Rational.landscape()); + // } + if (lifecycleState == AppLifecycleState.resumed) { + // Aplikasi kembali dari background (termasuk dari mode PiP) + // Menampilkan status bar kembali + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + } + } + + Future<void> enablePip() async { + final status = await floating.enable(Rational.landscape()); + debugPrint('Pipenable? $status'); + } + + @override + void deactivate() { + // Pauses video while navigating to next page. + _controller.pause(); + // _chewieController.pause(); + super.deactivate(); + } + + // void listener() { + // if (_isPlayerReady && mounted && !_controller.value.isFullScreen) { + // setState(() { + // _playerState = _controller.value.playerState; + // _videoMetaData = _controller.metadata; + // }); + // } + // if (_playerState == PlayerState.ended) { + // print('Video telah selesai diputar'); + // } + // } + + void loadImage(String link) async { + final imageProvider = + Image.network("https://api.vokasia.id/uploads/lesson_files/" + link) + .image; + showImageViewer(context, imageProvider, doubleTapZoomable: true, + onViewerDismissed: () { + print("dismissed"); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, + overlays: SystemUiOverlay.values); + }); + } + + Future<void> openFileFromUrl(BuildContext context, String fileUrl) async { + try { + print(fileUrl); + final response = await http.get( + Uri.parse('https://api.vokasia.id/uploads/lesson_files/$fileUrl')); + if (response.statusCode == 200) { + final fileBytes = response.bodyBytes; + final fileName = fileUrl.split('/').last; + final fileType = 'txt'; // Sesuaikan dengan tipe file yang ingin dibuka + + final tempDir = await getTemporaryDirectory(); + final tempFilePath = '${tempDir.path}/$fileName'; + + await File(tempFilePath).writeAsBytes(fileBytes); + print(tempFilePath); + OpenFile.open(tempFilePath); + // Buka file menggunakan aplikasi default pada perangkat + // Contoh: OpenFile.open(tempFilePath); + } else { + print('Failed to download file: ${response.statusCode}'); + } + } catch (e) { + print('Error opening file from URL: $e'); + } + } + + void resetExpansionTileKeysAndSelectedIndex() { + _tileKeys.clear(); + _selectedIndex = 0; + } + + @override + Widget build(BuildContext context) { + CurrentLessonProvider currentLesson = + Provider.of<CurrentLessonProvider>(context, listen: false); + + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + String _parseHtmlString(String htmlText) { + RegExp exp = RegExp(r"<[^>]*>| |&|"", + multiLine: true, caseSensitive: true); + return htmlText.replaceAll(exp, ''); + } + + resetExpansionTileKeysAndSelectedIndex(); + final selectedTitleProvider = Provider.of<SelectedTitleProvider>(context); + PlayVideoCourseProvider playVideoCourseProvider = + Provider.of<PlayVideoCourseProvider>(context); + var dataProgress = Provider.of<myCourseProvider.MyCourseProvider>(context); + // var dataProgress22 = Provider.of<myCourseProvider.MyCourseProvider>(context).courseService; + + _getTab(index, child) { + return Tab( + height: 30, + iconMargin: EdgeInsets.zero, + child: Container( + child: Center( + child: Text( + child, + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.2, + letterSpacing: 0.4, + ), + ), + ), + ), + ); + } + + _ceklisattachment( + String lessonId, + String idCourse, + int isFinished, + ) async { + print('ini lesson id ->' + lessonId); + print('ini course id ->' + idCourse); + await lessonCourseService.updateLessonCourse(lessonId).then((value) => { + print('ini value dari sana' + value.toString()), + if (value == '201') + setState(() { + { + isFinished = 1; + } + }) + }); + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonId) + .first + .isFinished = 1; + }); + } + + // Widget cekbox( + // int isFinished, String isSkipped, String courseId, String lessonId) { + // return Checkbox( + // shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)), + // activeColor: primaryColor, + // value: (isFinished == 1) ? true : false, + // onChanged: (value) { + // if (isSkipped == '1') { + // Provider.of<LessonCourseProvider>(context) + // .updateLessonCourse(courseId, lessonId); + // } + // }); + // } + + Duration getVideoDuration(String duration) { + int hours = 0; + int minutes = 0; + int seconds = 0; + List<String> timeSplit = duration.split(':'); + hours = int.parse(timeSplit[0]); + minutes = int.parse(timeSplit[1]); + seconds = int.parse(timeSplit[2]); + return Duration(hours: hours, minutes: minutes, seconds: seconds - 1); + } + + Widget cekbox( + int isFinished, + String isSkipped, + String lessonId, + Map<String?, dynamic> lessonMapId, + List<DataLesson> dataLesson, + String lessontypeee, + String videotypeee, + String idCourse) { + return Theme( + data: Theme.of(context).copyWith(unselectedWidgetColor: Colors.grey), + child: Checkbox( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4)), + activeColor: primaryColor, + checkColor: Colors.white, + value: (isFinished == 1) ? true : false, + onChanged: (value) async { + // print(lessontypeee); + var data = dataLesson + .map((e) => e) + .where((lesson) => + lesson.lessonId == + lessonMapId['${playVideoCourseProvider.url}']) + .first; + if (lessontypeee != 'video') { + //todo + if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Anda tidak dapat mencentang pelajaran\nyang tidak dapat diskip', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } else { + print('ini lesson id ->' + lessonId); + print('ini course id ->' + idCourse); + await lessonCourseService + .updateLessonCourse(lessonId) + .then((value) => { + print('ini value dari sana' + value.toString()), + if (value == '201') + setState(() { + { + isFinished = 1; + } + }) + }); + await Provider.of<myCourseProvider.MyCourseProvider>( + context, + listen: false) + .getMyCourse(); + setState(() { + print('DIBAWAH CUY'); + dataLesson + .map((e) => e) + .where((element) => element.lessonId == lessonId) + .first + .isFinished = 1; + }); + } + } else if (videotypeee == 'html5') { + print('ceklis html5 diklik'); + + if (isSkipped == '1') { + _chewieController.seekTo(getVideoDuration(data.duration!)); + } else if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + } + + if (lessonId == lessonMapId['${playVideoCourseProvider.url}'] && + lessonId != '') { + if (isSkipped == '1') { + print('masuk kesini'); + _controller.seekTo(getVideoDuration(data.duration!)); + } else if (isSkipped == '0') { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + } + })); + } + + Widget tileKursus( + Key tileKeyoper, + int index, + List lesonOper, + Iterable<Datum> sectionOper, + Datum e, + Map<String?, dynamic> lessonMapIdoper, + List<DataLesson> dataLessonOper) { + return ListTileTheme( + dense: true, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 15, vertical: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + spreadRadius: 0.01, + blurRadius: 1, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: ExpansionTileCopy( + key: tileKeyoper, + initiallyExpanded: index == _expandedTileIndex, + onExpansionChanged: (value) { + // If tile is expanding, then collapse the already expanded tile. + if (value) { + if (index != _selectedIndex) { + _tileKeys[_selectedIndex].currentState!.closeExpansion(); + } + _selectedIndex = index; + } + }, + title: Text( + 'Bab ${index + 1}', + style: thirdTextStyle.copyWith( + fontWeight: bold, + letterSpacing: 0.5, + color: Theme.of(context).colorScheme.onBackground, + fontSize: getProportionateScreenWidth(12)), + ), + subtitle: Html( + shrinkWrap: true, + data: e.sectionTitle, + style: { + "body": Style( + margin: Margins.zero, + padding: HtmlPaddings.zero, + fontSize: FontSize(getProportionateScreenWidth(12)), + fontWeight: semiBold, + letterSpacing: 0.5, + fontFamily: 'Poppins', + color: Theme.of(context).colorScheme.onBackground), + }, + ), + children: e.dataLesson! + .asMap() + .entries + .map( + (e) => Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(20), + bottom: getProportionateScreenHeight(5)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10)), + child: GestureDetector( + onTap: _isPlayerReady + ? (e.value.videoType == 'youtubelink' || + e.value.videoType == 'YouTube') + ? () { + setState(() { + _ytCurrentTime = + double.parse(e.value.progress ?? '0') + .round(); + _isvideoClicked = true; + _isPlayerReady = true; + _setLocalLEssonId = e.value.lessonId; + }); + print('masuk sini $_ytCurrentTime'); + selectedTitleProvider.selectedTitle = + e.value.title; + Announcement announcement = Announcement( + id: lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + lesonOper: lesonOper, + sectionOper: sectionOper, + lessonMapIdoper: lessonMapIdoper, + dataLessonOper: dataLessonOper, + ); + announcement.showSummary(e.value.title); + print(_gabisaskip); + if (_gabisaskip == true) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + )); + } else { + if (e.value.isSkip == '1') { + setState(() { + _gabisaskip = false; + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url, + ); + _isyt = true; + } else { + print( + "MASUK SINI KAH?? ---------------------> $_ytCurrentTime"); + print( + "link ------------------------------------------------>${YoutubePlayer.convertUrlToId(playVideoCourseProvider.url)!}"); + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider.url)!, + startAt: _ytCurrentTime, + ); + _isyt = true; + } + _isyt = true; + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 1) { + setState(() { + _gabisaskip = false; + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url, + ); + _isyt = true; + } else { + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider + .url) ?? + '', + startAt: _ytCurrentTime, + ); + _isyt = true; + } + _isyt = true; + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 0) { + //todo + setState(() { + _gabisaskip = true; + + playVideoCourseProvider.indexUri( + YoutubePlayer.convertUrlToId( + e.value.videoUrl ?? '') ?? + ''); + if (_isyt == false) { + _initializeytController( + playVideoCourseProvider.url, + ); + _isyt = true; + } else { + _controller.load( + YoutubePlayer.convertUrlToId( + playVideoCourseProvider + .url) ?? + '', + startAt: _ytCurrentTime, + ); + _isyt = true; + } + _isyt = true; + }); + } + } + } + : (e.value.videoType == 'html5') + ? () { + setState(() { + _isvideoClicked = true; + _isPlayerReady = true; + }); + print(_gabisaskip); + if (_gabisaskip == true) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Video Tidak dapat dilewati', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + )); + } else { + if (e.value.isSkip == '1') { + setState(() { + _gabisaskip = false; + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 1) { + setState(() { + _gabisaskip = false; + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } else if (e.value.isSkip == '0' && + e.value.isFinished == 0) { + //todo + setState(() { + _gabisaskip = true; + + setState(() { + //todo + print(e.value.courseId); + courseID_local_end = + e.value.courseId.toString(); + lessonID_local_end = + e.value.lessonId.toString(); + print(e.value.videoUrl); + _isyt = false; + _initializeChewieController( + '${e.value.videoUrl}', + ); + }); + }); + } + } + } + : () { + (e.value.lessonType == 'quiz') + ? { + print('ini quiz'), + (_isyt == true) + ? { + _controller.pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId.toString(), + widget.courseeid, + e.value.isFinished!), + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + quizPage( + judulQuiz: e.value.title + .toString(), + lessonId: e + .value.lessonId + .toString(), + ), + )) + } + : (e.value.attachment + .toString() + .contains('.pdf')) + ? { + print('ini pdf'), + (_isyt == true) + ? { + _controller.pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget.courseeid, + e.value.isFinished!), + // print(e.value.lessonId.toString()), + // print(widget.courseeid), + // print(e.value.isFinished.toString()), + // _chewieController.pause(), + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + pdfReader( + link: e.value + .attachment, + title: e.value.title + .toString(), + ), + )) + } + : (e.value.attachment + .toString() + .contains( + '.xlsx')) || + (e.value.attachment + .toString() + .contains('.xls')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget.courseeid, + e.value + .isFinished!), + openFileFromUrl(context, + e.value.attachment) + } + : (e.value.attachment + .toString() + .contains( + '.pptx')) || + (e.value.attachment + .toString() + .contains( + '.ppt')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value.lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value.attachment + .toString() + .contains( + '.rar')) + ? { + (_isyt == true) + ? { + _controller + .pause(), + } + : { + _videoPlayerController + .pause() + }, + _ceklisattachment( + e.value + .lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value + .attachment + .toString() + .contains( + '.zip')) + ? { + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment( + e.value + .lessonId + .toString(), + widget + .courseeid, + e.value + .isFinished!), + openFileFromUrl( + context, + e.value + .attachment) + } + : (e.value.attachment.toString().contains('.docx')) + ? { + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment( + e.value.lessonId.toString(), + widget.courseeid, + e.value.isFinished!), + openFileFromUrl( + context, + e.value.attachment) + } + : ((e.value.attachment.toString().contains('.jpg') || e.value.attachment.toString().contains('.jpeg') || e.value.attachment.toString().contains('.png'))) + ? { + (e.value.attachment == null) + ? { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Dokumen Belum Tersedia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )) + } + : { + print('ini image'), + // _controller.pause(), + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment(e.value.lessonId.toString(), widget.courseeid, e.value.isFinished!), + + loadImage(e.value.attachment) + }, + } + : (e.value.attachment.toString().contains('.txt')) + ? { + (e.value.attachment == null) + ? { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Dokumen Belum Tersedia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )) + } + : { + print('ini text'), + (_isyt == + true) + ? { + _controller.pause(), + } + : { + _videoPlayerController.pause() + }, + _ceklisattachment(e.value.lessonId.toString(), widget.courseeid, e.value.isFinished!), + openFileFromUrl(context, e.value.attachment) + }, + } + : ScaffoldMessenger.of(context).showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Bukan video url ', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + } + : () { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Mohon tunggu player belum siap ', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + )); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + cekbox( + e.value.isFinished ?? 0, + e.value.isSkip ?? '0', + e.value.lessonId ?? '', + lessonMapIdoper, + dataLessonOper, + e.value.lessonType.toString(), + e.value.videoType.toString(), + widget.courseeid), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + // color: Colors.amber, + width: getProportionateScreenWidth(190), + child: Text( + _parseHtmlString( + e.value.title.toString()), + style: thirdTextStyle.copyWith( + fontSize: 12, + color: Theme.of(context) + .colorScheme + .onBackground, + // letterSpacing: 0.5, + ), + ), + ), + Text( + (e.value.lessonType == 'video') + ? 'Video - ${formatDuration(e.value.duration.toString())}' + : (e.value.lessonType == 'quiz') + ? 'Quiz' + : (e.value.attachment + .toString() + .contains('.pdf')) + ? 'PDFs' + : (e.value.attachment + .toString() + .contains('.pptx')) + ? 'PPT' + : (e.value.attachment + .toString() + .contains('.rar')) + ? 'RAR' + : (e.value.attachment + .toString() + .contains( + '.zip')) + ? 'ZIP' + : (e.value + .attachment + .toString() + .contains( + '.xlsx')) + ? 'Excel' + : (e.value.attachment.toString().contains('.jpg') || + e.value + .attachment + .toString() + .contains( + '.jpeg') || + e.value + .attachment + .toString() + .contains( + '.png')) + ? 'Image' + : (e.value + .attachment + .toString() + .contains('.docx')) + ? 'Document' + : (e.value.attachment.toString().contains('.txt')) + ? 'Text' + : 'Terjadi Kesalahan', + style: TextStyle( + fontSize: 10, + color: Theme.of(context) + .colorScheme + .onBackground, + letterSpacing: 0.5, + fontFamily: 'Noto Sans', + ), + ), + ], + ), + Spacer(), + if (e.value.lessonType == 'video') + Image.asset( + 'assets/images/play_button_new.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(3), + ) + else if (e.value.lessonType == 'quiz') + Image.asset( + 'assets/icons/lms/ListNumbers.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.pdf')) + Image.asset( + 'assets/icons/lms/FilePdf.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.rar')) + Image.asset( + 'assets/icons/lms/FileArchive.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.zip')) + Image.asset( + 'assets/icons/lms/FileZip.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.pptx')) + Image.asset( + 'assets/icons/lms/FilePpt.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.xlsx')) + Image.asset( + 'assets/icons/lms/FileXls.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.jpg') || + e.value.attachment + .toString() + .contains('.jpeg') || + e.value.attachment + .toString() + .contains('.png')) + Image.asset( + 'assets/icons/lms/FileImage.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if (e.value.attachment + .toString() + .contains('.txt')) + Image.asset( + 'assets/icons/lms/FileText.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else if ((e.value.attachment + .toString() + .contains('.docx'))) + Image.asset( + 'assets/icons/lms/FileDoc.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth(1.3), + ) + else + SizedBox(), + ], + ), + ], + ), + ), + ), + ) + .toList(), + ), + )); + } + + Widget tabbarbawah( + BuildContext context, + List lesonOper, + Iterable<Datum> sectionOper, + Map<String?, dynamic> lessonMapIdoper, + List<DataLesson> dataLessonOper) { + return DefaultTabController( + length: 4, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + spreadRadius: 0.05, + blurRadius: 5, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(10), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: Text( + '${widget.judul}', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14), + fontWeight: semiBold), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + vertical: getProportionateScreenHeight(8)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('${widget.instruktur}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: isDarkMode + ? Color(0xff727272) + : Color(0xff727272), + )), + GestureDetector( + onTap: () { + _controller.pause(); + _videoPlayerController.pause(); + + Navigator.of(context, rootNavigator: true).push( + MaterialPageRoute( + builder: (context) => Sertif( + totalProgress: totalProgress, + idCourse: int.parse(widget.courseeid), + ), + ), + ); + }, + child: Row( + children: [ + Image.asset( + 'assets/images/certificate_icon.png', + scale: getProportionateScreenWidth(1.3), + ), + SizedBox( + width: 5, + ), + Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + color: primaryColor), + ) + ], + ), + ) + ], + ), + ), + Consumer<myCourseProvider.MyCourseProvider>( + builder: (context, state, _) { + var progres = state.result!.data[0] + .map((e) => e) + .where( + (element) => + element.courseId == + lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + ) + .toList(); + print(progres[0]); + double progressWidth = (SizeConfig.screenWidth - + getProportionateScreenWidth(20)) * + int.parse(progres[0].totalProgress.toString()) / + 100; + print("ini progres width" + progressWidth.toString()); + + totalProgress = progres[0].totalProgress!; + return (progressWidth == 0.0) + ? Padding( + padding: EdgeInsets.only( + left: progressWidth, + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))) + : (progressWidth > 390) + ? Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10), + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: + getProportionateScreenWidth(35), + height: + getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: + BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + // '${progressWidth}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))), + ) + : Padding( + padding: EdgeInsets.only( + left: progressWidth - + getProportionateScreenWidth(5), + + // : progressWidth > 390 + // ? progressWidth - 25 + // : progressWidth - 10, + ), + child: Container( + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(15), + // padding: EdgeInsets.only(left: progressWidth - 5), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: + BorderRadius.circular(5)), + child: Center( + child: Text( + '${progres[0].totalProgress}%', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ))); + }, + ), + SizedBox( + height: getProportionateScreenWidth(5), + ), + Consumer<myCourseProvider.MyCourseProvider>( + builder: (context, state, _) { + var progres = state.result!.data[0] + .map((e) => e) + .where( + (element) => + element.courseId == + lesonOper + .map((e) => e.courseId ?? '') + .toList() + .first, + ) + .toList(); + // print(progres[0]); + double progressWidth = (SizeConfig.screenWidth - + getProportionateScreenWidth(20)) * + int.parse(progres[0].totalProgress.toString()) / + 100; + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Stack( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenWidth(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey[300], + ), + ), + Container( + width: progressWidth, + height: getProportionateScreenWidth(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor, + ), + ), + ], + ), + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(24)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: TabBar( + isScrollable: true, + padding: EdgeInsets.zero, + unselectedLabelColor: + Theme.of(context).colorScheme.onBackground, + labelColor: primaryColor, + indicatorColor: primaryColor, + controller: _tabController, + labelPadding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + tabAlignment: TabAlignment.start, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + tabs: [ + _getTab(0, 'Konten'), + _getTab(1, 'Detail'), + _getTab(2, 'Q & A'), + _getTab(3, 'Pengumuman'), + ], + ), + ), + ], + ), + ), + + // Container( + // width: double.infinity, + // height: 1, + // decoration: BoxDecoration(boxShadow: [ + // BoxShadow( + // blurRadius: 5, spreadRadius: 0.1, offset: Offset(0, 10)) + // ]), + // ), + SizedBox( + height: 500, + child: TabBarView( + // physics: NeverScrollableScrollPhysics(), + controller: _tabController, + children: [ + Column( + children: [ + SizedBox( + height: getProportionateScreenHeight(15), + ), + SizedBox( + height: getProportionateScreenHeight(5), + ), + Expanded( + child: ListView.builder( + itemCount: sectionOper.length, + itemBuilder: (context, index) { + final tileKey = GlobalKey(); + _tileKeys.add(tileKey); + var e = sectionOper.toList()[index]; + return Theme( + data: ThemeData.dark().copyWith( + colorScheme: + ColorScheme.dark(primary: secondaryColor), + dividerColor: Colors.transparent, + ), + child: Container( + margin: EdgeInsets.only(bottom: 5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10)), + child: Column( + children: [ + tileKursus( + tileKey, + index, + lesonOper, + sectionOper, + e, + lessonMapIdoper, + dataLessonOper) + ], + ), + ), + ); + }), + ), + ], + ), + DetailPlayCourse(), + QuestAndAnswer( + id: lesonOper.map((e) => e.courseId ?? '').toList().first, + idLesson: + lesonOper.map((e) => e.lessonId ?? '').toList().first, + ), + Announcement( + id: lesonOper.map((e) => e.courseId ?? '').toList().first, + lesonOper: lesonOper, + sectionOper: sectionOper, + lessonMapIdoper: lessonMapIdoper, + dataLessonOper: dataLessonOper, + ), + ], + ), + ) + ], + ), + ); + } + + return Container( + color: Colors.black, + child: SafeArea( + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + key: globalScaffoldKey, + body: Consumer<LessonCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.loading) { + return const Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ); + } else if (state.state == ResultState.hasData) { + var leson = state.result!.data[0]; + var section = state.sectionResult!.data[0].values; + + section.forEach((element) => element.dataLesson! + .forEach((element) => dataLesson.add(element))); + + List<String> _listVideoUrl = dataLesson + .map((e) => + YoutubePlayer.convertUrlToId(e.videoUrl ?? '') ?? '') + .where((element) => element.isNotEmpty) + .toList(); + Map<String?, dynamic> lessonMapId = Map.fromIterable(dataLesson, + key: (e) => YoutubePlayer.convertUrlToId(e.videoUrl ?? ''), + value: (e) => e.lessonId) + ..removeWhere((key, value) => key == null || value == null); + _isPlayerReady = true; + return SingleChildScrollView( + child: Column( + children: [ + if (_isvideoClicked == false) + AspectRatio( + aspectRatio: 16 / 9, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget.thumbnail), + ), + ), + ), + ) + else if (_isvideoClicked == true && _isyt == true) + PiPSwitcher( + childWhenEnabled: YoutubePlayerBuilder( + player: YoutubePlayer( + controller: _controller, + ), + builder: (p0, p1) { + return YoutubePlayer( + thumbnail: Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget.thumbnail), + ), + ), + ), + showVideoProgressIndicator: true, + progressColors: const ProgressBarColors( + handleColor: primaryColor, + bufferedColor: Colors.white24, + playedColor: primaryColor, + backgroundColor: Colors.white24, + ), + progressIndicatorColor: primaryColor, + topActions: [ + GestureDetector( + onTap: () {}, + child: Icon(Icons.expand_more), + ), + Spacer(), + Switch( + inactiveThumbImage: AssetImage( + 'assets/images/switch.png'), + activeColor: tenthColor, + value: state.switchbutton, + onChanged: (s) { + state.autoplay(); + }, + ), + ], + onEnded: (state.switchbutton) + ? (data) async { + print('masuk sini'); + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + setState(() async { + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + + await Provider.of< + myCourseProvider + .MyCourseProvider>( + context, + listen: false, + ).getMyCourse().then((value) => + _controller.load( + _listVideoUrl[ + (_listVideoUrl.indexOf( + data.videoId) + + 1) % + _listVideoUrl.length], + )); + + playVideoCourseProvider.indexUri( + _listVideoUrl[(_listVideoUrl + .indexOf(data.videoId) + + 1) % + _listVideoUrl.length], + ); + } + : (d) async { + //todo + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + await Provider.of< + myCourseProvider + .MyCourseProvider>( + context, + listen: false, + ).getMyCourse(); + setState(() { + print('KELARR DISINIII'); + _gabisaskip = false; + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + // print('g masuk'); + }, + onReady: () { + _controller.load( + YoutubePlayer.convertUrlToId( + _listVideoUrl.first) ?? + '', + ); + + _isPlayerReady = true; + playVideoCourseProvider.uri = + _listVideoUrl.first; + }, + controller: _controller, + ); + }, + ), + childWhenDisabled: YoutubePlayerBuilder( + player: YoutubePlayer( + controller: _controller, + ), + builder: (p0, p1) { + return YoutubePlayer( + thumbnail: Container( + decoration: BoxDecoration( + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget.thumbnail), + ), + ), + ), + showVideoProgressIndicator: true, + progressColors: const ProgressBarColors( + handleColor: primaryColor, + bufferedColor: Colors.white24, + playedColor: primaryColor, + backgroundColor: Colors.white24, + ), + progressIndicatorColor: primaryColor, + topActions: [ + GestureDetector( + onTap: () {}, + child: Icon(Icons.expand_more), + ), + Spacer(), + Switch( + inactiveThumbImage: AssetImage( + 'assets/images/switch.png'), + activeColor: tenthColor, + value: state.switchbutton, + onChanged: (s) { + state.autoplay(); + }, + ), + Padding( + padding: EdgeInsets.only(left: 5), + child: GestureDetector( + onTap: enablePip, + child: Icon(Icons.picture_in_picture), + ), + ) + ], + onEnded: (state.switchbutton) + ? (data) async { + print('masuk sini'); + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + setState(() async { + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + + await Provider.of< + myCourseProvider + .MyCourseProvider>( + context, + listen: false, + ).getMyCourse().then((value) => + _controller.load( + _listVideoUrl[ + (_listVideoUrl.indexOf( + data.videoId) + + 1) % + _listVideoUrl.length], + )); + + playVideoCourseProvider.indexUri( + _listVideoUrl[(_listVideoUrl + .indexOf(data.videoId) + + 1) % + _listVideoUrl.length], + ); + } + : (d) async { + //todo + await state.updateLessonCourse( + leson + .map((e) => e.courseId ?? '') + .toList() + .first, + lessonMapId[ + '${playVideoCourseProvider.url}'], + ); + await Provider.of< + myCourseProvider + .MyCourseProvider>( + context, + listen: false, + ).getMyCourse(); + setState(() { + print('KELARR DISINIII'); + _gabisaskip = false; + dataLesson + .map((e) => e) + .where((element) => + element.lessonId == + lessonMapId[ + '${playVideoCourseProvider.url}']) + .first + .isFinished = 1; + }); + // print('g masuk'); + }, + onReady: () { + _controller.load( + YoutubePlayer.convertUrlToId( + _listVideoUrl.first) ?? + '', + ); + + _isPlayerReady = true; + playVideoCourseProvider.uri = + _listVideoUrl.first; + }, + controller: _controller, + ); + }, + )) + else if (_isvideoClicked == true && + _chewieController == '') + CircularProgressIndicator() + else if (_isvideoClicked == true && _isyt == false) + PiPSwitcher( + childWhenEnabled: AspectRatio( + aspectRatio: 16 / 9, + child: Chewie(controller: _chewieController), + ), + childWhenDisabled: AspectRatio( + aspectRatio: 16 / 9, + child: Chewie(controller: _chewieController), + ), + ), + + tabbarbawah( + context, leson, section, lessonMapId, dataLesson), + + // Flexible( + // child: tabbarbawah( + // context, leson, section, lessonMapId, dataLesson), + // ) + ], + ), + ); + } else if (state.state == ResultState.error) { + Future.delayed(Duration.zero, () { + Navigator.pop(context); + CherryToast.error( + animationDuration: Durations.long1, + title: Text("Kursus tidak memiliki materi pembelajaran", + style: TextStyle( + color: Colors.black, + fontSize: 15, + )), + animationType: AnimationType.fromTop, + ).show(context); + }); + return Center( + child: Column( + children: [ + Text( + '', + style: thirdTextStyle, + ), + ], + )); + } else { + return Center( + child: Text( + 'Terjadi kesalahan', + style: thirdTextStyle, + ), + ); + } + }, + ), + ), + ), + ); + } +} diff --git a/lib/screens/course/quiz_page.dart b/lib/screens/course/quiz_page.dart new file mode 100644 index 0000000..6a50064 --- /dev/null +++ b/lib/screens/course/quiz_page.dart @@ -0,0 +1,161 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/quiz_model.dart'; +import 'package:initial_folder/screens/course/quiz_question_page.dart'; +import 'package:initial_folder/services/quiz_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; + +class quizPage extends StatefulWidget { + final String judulQuiz; + final String lessonId; + const quizPage({super.key, required this.judulQuiz, required this.lessonId}); + + @override + State<quizPage> createState() => _quizPageState(); +} + +class _quizPageState extends State<quizPage> { + QuizModel? quizModel; + + @override + void initState() { + super.initState(); + print(widget.judulQuiz); + print(widget.lessonId); + getQuiz(); + } + + void getQuiz() { + quiz_service().get_quiz_info(widget.lessonId).then((value) => { + setState(() { + quizModel = value; + print("ini id apa gatau" + quizModel!.data.first.quizId); + }) + }); + } + + Widget build(BuildContext context) { + return quizModel == null + ? Center( + child: Scaffold( + body: Center( + child: CircularProgressIndicator( + color: primaryColor, + ), + ), + )) + : Scaffold( + appBar: AppBar( + centerTitle: true, + backgroundColor: Colors.transparent, + ), + body: SingleChildScrollView( + child: Center( + child: Column( + children: [ + Container( + margin: EdgeInsets.all(25), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == + Brightness.dark + ? Colors.black + : Colors.grey, + blurRadius: 2, + spreadRadius: 1, + offset: Offset(0, 3)) + ]), + child: Column( + children: [ + SizedBox( + height: getProportionateScreenHeight(71), + ), + Image.asset( + 'assets/images/quizLogo-page.png', + scale: 0.8, + ), + SizedBox( + height: getProportionateScreenHeight(36), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 30), + child: Text( + widget.judulQuiz, + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: bold, + fontSize: getProportionateScreenWidth(15)), + ), + ), + SizedBox( + height: getProportionateScreenHeight(10), + ), + Text( + "Jumlah Pertanyaan : " + + quizModel!.total.toString(), + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: bold, + fontSize: getProportionateScreenWidth(15)), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + Padding( + padding: EdgeInsets.all( + getProportionateScreenHeight(15)), + child: TapDebouncer( + cooldown: const Duration(milliseconds: 1000), + onTap: () async => await { + if (quizModel!.total.toString() != "0") + { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + quiz_questionPage( + judulQuiz: widget.judulQuiz, + lessonId: widget.lessonId, + totalQuiz: quizModel!.total, + quizId: int.parse(quizModel! + .data.first.quizId), + ))) + } + else + { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Pertanyaan untuk quiz ini \nbelum tersedia", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + } + }, + builder: (BuildContext context, + TapDebouncerFunc? onTap) { + return DefaultButton( + text: 'Mulai Quiz', press: onTap); + }, + )) + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/course/quiz_question_page.dart b/lib/screens/course/quiz_question_page.dart new file mode 100644 index 0000000..ed456c3 --- /dev/null +++ b/lib/screens/course/quiz_question_page.dart @@ -0,0 +1,225 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/quiz_model.dart'; +import 'package:initial_folder/models/quiz_question_model.dart'; +import 'package:initial_folder/screens/course/quiz_result_page.dart'; +import 'package:initial_folder/services/quiz_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; +import 'package:html/parser.dart'; + +class quiz_questionPage extends StatefulWidget { + final String judulQuiz; + final String lessonId; + final int totalQuiz; + final int quizId; + const quiz_questionPage( + {super.key, + required this.judulQuiz, + required this.lessonId, + required this.totalQuiz, + required this.quizId}); + + @override + State<quiz_questionPage> createState() => _quiz_questionPageState(); +} + +class _quiz_questionPageState extends State<quiz_questionPage> { + int currentQuestionIndex = 0; // Menyimpan indeks pertanyaan saat ini + QuizModel? quizModel; + String? selectedOption; + bool isSelected = false; + List<Map<String, dynamic>> savedAnswers = []; + + @override + void initState() { + super.initState(); + + getQuiz(); + } + + void getQuiz() { + quiz_service().get_quiz_info(widget.lessonId).then((value) { + setState(() { + quizModel = value; + }); + }); + } + + Widget build(BuildContext context) { + String _parseHtmlString(String htmlText) { + RegExp exp = + RegExp(r"<[^>]*>| ", multiLine: true, caseSensitive: true); + + return htmlText.replaceAll(exp, ''); + } + + return quizModel == null + ? Center( + child: Scaffold( + body: Center( + child: CircularProgressIndicator( + color: primaryColor, + ), + ), + )) + : Scaffold( + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Pertanyaan ${currentQuestionIndex + 1}', // Menampilkan nomor pertanyaan + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(15), + ), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 30), + child: Text( + _parseHtmlString(quizModel!.data[currentQuestionIndex] + .title), // Menampilkan judul pertanyaan + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(15), + ), + ), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + Container( + width: getProportionateScreenWidth(350), + height: getProportionateScreenHeight(400), + // color: Colors.amber, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: quizModel!.data[currentQuestionIndex].options + .map((option) { + return Padding( + padding: EdgeInsets.fromLTRB( + getProportionateScreenWidth(15), + getProportionateScreenHeight(5), + getProportionateScreenWidth(15), + getProportionateScreenHeight(5)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == + Brightness.dark + ? Colors.black + : Colors.grey, + blurRadius: 2, + spreadRadius: 1, + offset: Offset(0, 3)) + ]), + child: CheckboxListTile( + activeColor: primaryColor, + checkColor: Colors.white, + checkboxShape: RoundedRectangleBorder( + side: BorderSide( + color: + primaryColor), // Mengatur warna dan ketebalan outline checkbox + borderRadius: BorderRadius.circular(2), + ), + title: Text( + option, + style: thirdTextStyle.copyWith(fontSize: 14), + ), + value: selectedOption == option, + controlAffinity: ListTileControlAffinity + .leading, // Checkbox di sebelah kiri + onChanged: (value) { + setState(() { + selectedOption = value! ? option : null!; + isSelected = true; + }); + }, + ), + ), + ); + }).toList(), + ), + )), + SizedBox(height: 20), + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(100), + right: getProportionateScreenWidth(100)), + child: TapDebouncer( + cooldown: const Duration(milliseconds: 1000), + onTap: () async => await { + setState(() { + //ini save quiz + if (isSelected != false) { + savedAnswers.add({ + '"question_id"': + quizModel!.data[currentQuestionIndex].id, + '"answers"': [ + '"${quizModel!.data[currentQuestionIndex].options.indexOf(selectedOption!) + 1}"' + ] + }); + if (currentQuestionIndex < + quizModel!.data.length - 1) { + currentQuestionIndex++; + } else { + if (widget.totalQuiz == + currentQuestionIndex + 1) { + print('sudah habis'); + // print(savedAnswers); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => quiz_resultPage( + judulQuiz: widget.judulQuiz, + lessonId: widget.lessonId, + totalQuiz: quizModel!.total, + allAnswer: + savedAnswers.toString(), + IdQuiz: widget.quizId, + ))); + } + } + } else { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Silakhan Pilih salah satu \nJawaban yang tersedia", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context); + print('object'); + } + // print(savedAnswers); + isSelected = false; + }) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return DefaultButton( + text: 'Submit & Next', press: onTap); + }, + )) + ], + ), + ); + } +} diff --git a/lib/screens/course/quiz_result_page.dart b/lib/screens/course/quiz_result_page.dart new file mode 100644 index 0000000..5bbfd8a --- /dev/null +++ b/lib/screens/course/quiz_result_page.dart @@ -0,0 +1,500 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/quiz_model.dart'; +import 'package:initial_folder/models/quiz_perquestion_result_model.dart'; +import 'package:initial_folder/models/quiz_question_model.dart'; +import 'package:initial_folder/models/quiz_question_result_model.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/screens/course/quiz_page.dart'; +import 'package:initial_folder/services/quiz_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; +import 'package:html/parser.dart'; + +class quiz_resultPage extends StatefulWidget { + final String judulQuiz; + final String lessonId; + final int totalQuiz; + final String allAnswer; + final int IdQuiz; + const quiz_resultPage( + {super.key, + required this.judulQuiz, + required this.lessonId, + required this.totalQuiz, + required this.allAnswer, + required this.IdQuiz}); + + @override + State<quiz_resultPage> createState() => _quiz_resultPageState(); +} + +class _quiz_resultPageState extends State<quiz_resultPage> { + QuizQuestionResult? quizQuestionResult; + QuizPerQuestionResult? quizPerQuestionResult; + int currentQuestionIndex = 1; // Menyimpan indeks pertanyaan saat ini + bool fromAmbilLagi = false; + int _selectedQuesNum = 1; + String _selectedQuesText = ""; + List<Map<String, dynamic>> allAnswersList = []; + @override + void initState() { + super.initState(); + print("ini id apa gatau" + widget.IdQuiz.toString()); + + // Parsing the allAnswer JSON string to List + allAnswersList = + List<Map<String, dynamic>>.from(json.decode(widget.allAnswer)); + + getAnswerQuiz(); + getQuestionPerNumber(); + } + + void getAnswerQuiz() { + quiz_service().get_result_quiz(widget.allAnswer).then((value) { + setState(() { + quizQuestionResult = value; + }); + }); + } + + void getQuestionPerNumber() { + print(_selectedQuesNum); + quiz_service() + .get_result_quiz_pernumber(widget.IdQuiz, _selectedQuesNum) + .then((value) { + setState(() { + quizPerQuestionResult = value; + }); + }); + } + + bool _isUserSelectedAnswer(int questionId, int optionIndex) { + for (var answer in allAnswersList) { + if (answer['question_id'] == questionId && + answer['answers'].contains(optionIndex.toString())) { + return true; + } + } + return false; + } + + Widget build(BuildContext context) { + String _parseHtmlString(String htmlText) { + RegExp exp = + RegExp(r"<[^>]*>| ", multiLine: true, caseSensitive: true); + + return htmlText.replaceAll(exp, ''); + } + + int countCorrectAnswers() { + if (quizQuestionResult != null && quizQuestionResult!.data.isNotEmpty) { + // Menggunakan metode where untuk menyaring data yang memiliki is_correct true + int correctCount = quizQuestionResult!.data + .where((question) => question.isCorrect == true) + .length; + return correctCount; + } else { + return 0; + } + } + + return quizQuestionResult == null + ? Center( + child: Scaffold( + body: Center( + child: CircularProgressIndicator( + color: primaryColor, + ), + ), + )) + : PopScope( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + backgroundColor: Colors.transparent, + automaticallyImplyLeading: + false, // Menonaktifkan tombol kembali default + leading: IconButton( + // Menambahkan tombol kustom + icon: Icon(Icons.arrow_back), + onPressed: () { + // Tambahkan fungsi yang diinginkan saat tombol kembali ditekan + Future.delayed(Duration.zero, () { + Navigator.of(context).pop(); + }); + }, + ), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + spreadRadius: 1, + blurRadius: 5, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: Column( + children: [ + Container( + margin: EdgeInsetsDirectional.only( + top: getProportionateScreenHeight(15)), + height: getProportionateScreenHeight(110), + width: getProportionateScreenWidth(320), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == + Brightness.dark + ? Colors.black + : Colors.grey, + blurRadius: 2, + spreadRadius: 1, + offset: Offset(0, 0)) + ]), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Tinjau materi kursus untuk memperluas pembelajaran kamu.', // Menampilkan nomor pertanyaan + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: FontWeight.w200, + fontSize: getProportionateScreenWidth(13), + ), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + Text( + 'Kamu mendapatkan ${countCorrectAnswers()} Dari ${widget.totalQuiz} Benar', // Menampilkan nomor pertanyaan + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: FontWeight.w200, + fontSize: getProportionateScreenWidth(13), + ), + ), + ], + ), + ), + SizedBox( + height: 25, + ), + SizedBox( + height: 90, // Adjust height to control the size + + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: quizQuestionResult!.data.length, + itemBuilder: (context, index) { + var dataques = quizQuestionResult!.data[index]; + + return GestureDetector( + onTap: () { + setState(() { + _selectedQuesNum = index + 1; + // getQuestionPerNumber() + _selectedQuesText = dataques.question; + }); + getQuestionPerNumber(); + // print( + // quizPerQuestionResult!.data.first.title); + // print(dataques.question); + // print(dataques.questionId); + }, + child: SizedBox( + width: 80, // Adjust width here + height: 65, // Adjust height here + child: Container( + margin: EdgeInsets.all(10), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + borderRadius: + BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .brightness == + Brightness.dark + ? Colors.black + : Colors.grey, + blurRadius: 2, + spreadRadius: 1, + offset: Offset(0, 3)) + ]), + child: Column( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Center( + child: Text( + '${index + 1}', + // dataques.isCorrect.toString(), + style: thirdTextStyle.copyWith( + fontSize: 16, + fontWeight: FontWeight.w800), + ), + ), + SizedBox( + height: 5, + ), + Align( + alignment: Alignment.bottomCenter, + child: Container( + height: + getProportionateScreenHeight( + 18), + decoration: BoxDecoration( + color: dataques.isCorrect == + true + ? Colors.green + : dataques.isCorrect != true + ? Colors.red + : Colors.red, + borderRadius: + BorderRadius.vertical( + bottom: Radius.circular( + 15)), + ), + child: Center( + child: dataques.isCorrect == + true + ? Icon( + Icons + .check_circle_outline, + color: Colors.white, + size: + getProportionateScreenHeight( + 13), + ) + : dataques.isCorrect != + true + ? Icon( + Icons + .cancel_outlined, + color: + Colors.white, + size: + getProportionateScreenHeight( + 13), + ) + : Icon( + Icons + .cancel_outlined, + color: + Colors.white, + size: + getProportionateScreenHeight( + 13), + )), + ), + ), + ], + )), + ), + ); + }, + ), + ), + SizedBox( + height: 15, + ) + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + Text( + 'Pertanyaan ${_selectedQuesNum}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14)), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + Text( + _parseHtmlString(quizPerQuestionResult!.data.first.title), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14)), + ), + Expanded( + child: ListView.builder( + scrollDirection: Axis.vertical, + itemCount: + quizPerQuestionResult!.data.first.options.length, + itemBuilder: (context, index) { + var dataques = quizPerQuestionResult!.data.first; + bool isCorrectAnswer = dataques.correctAnswers + .contains((index + 1).toString()); + return Padding( + padding: EdgeInsets.all(10), + child: Container( + decoration: BoxDecoration( + color: fourthColor, + borderRadius: BorderRadius.circular(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: getProportionateScreenHeight(45), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + borderRadius: + BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .brightness == + Brightness.dark + ? Colors.black + : Colors.grey, + blurRadius: 5, + spreadRadius: 3, + offset: Offset(0, 0)) + ]), + child: Row( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 25), + height: + getProportionateScreenHeight( + 20), + width: + getProportionateScreenWidth(25), + decoration: BoxDecoration( + color: isCorrectAnswer + ? Colors.green + : _isUserSelectedAnswer( + int.parse( + dataques.id), + index + 1) + ? Colors.red + : Colors.transparent, + borderRadius: BorderRadius.all( + Radius.circular(10)), + border: Border.all( + color: isCorrectAnswer + ? Colors.green + : _isUserSelectedAnswer( + int.parse( + dataques + .id), + index + 1) + ? Colors.red + : Colors.grey, + width: 2)), + child: Center( + child: isCorrectAnswer + ? Icon( + Icons.check, + color: Colors.white, + weight: 5, + ) + : _isUserSelectedAnswer( + int.parse( + dataques.id), + index + 1) + ? Icon( + Icons.check, + color: Colors.white, + weight: 5, + ) + : SizedBox(), + ), + ), + Text( + ' ${_parseHtmlString(dataques.options[index])}', + style: thirdTextStyle.copyWith( + color: isCorrectAnswer + ? Colors.green + : _isUserSelectedAnswer( + int.parse( + dataques.id), + index + 1) + ? Colors.red + : Colors.grey, + fontSize: + getProportionateScreenWidth( + 12)), + ), + ], + ), + ), + ], + ))); + +// Helper function to check if the current option is the user's selected answer + }, + ), + ), + // SingleChildScrollView(child: ,), + Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(20), + top: getProportionateScreenHeight(10)), + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(100), + right: getProportionateScreenWidth(100)), + child: DefaultButton( + text: 'Ambil Lagi', + press: () { + setState(() { + fromAmbilLagi = true; + _quizscr(context); + }); + }, + ), + ), + ), + ) + ], + ), + ), + onPopInvoked: (didPop) => _willPop(context), + ); + } + + void _willPop(BuildContext context) { + Future.delayed(Duration.zero, () { + if (fromAmbilLagi != true) { + Navigator.of(context)..pop(); + } + Navigator.of(context) + .pop(); // Hanya pop satu halaman jika tidak dari _quizscr + fromAmbilLagi = false; + }); + } + + void _quizscr(BuildContext context) { + Navigator.pop(context); + } +} diff --git a/lib/screens/course/search_my_course_page.dart b/lib/screens/course/search_my_course_page.dart new file mode 100644 index 0000000..5cfb29b --- /dev/null +++ b/lib/screens/course/search_my_course_page.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/widgets/loading/loading_my_course.dart'; +import 'package:initial_folder/widgets/my_course_list.dart'; +import 'package:provider/provider.dart'; +import '../../theme.dart'; + +class SearchMyCourse extends StatelessWidget { + const SearchMyCourse({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + TextEditingController controller = TextEditingController(); + + void refreshCourseList() { + Provider.of<MyCourseProvider>(context, listen: false) + .getSearchMyCourse(controller.text); + } + + return Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: TextField( + autofocus: true, + decoration: new InputDecoration.collapsed( + hintText: 'Cari Kursusku', + ), + controller: controller, + onSubmitted: (value) => refreshCourseList(), + ), + ), + body: RefreshIndicator( + displacement: 40, + color: primaryColor, + onRefresh: () async { + refreshCourseList(); + }, + child: Consumer<MyCourseProvider>( + builder: (BuildContext context, state, _) { + if (state.searchResultState == SearchResultState.Loading) { + return SingleChildScrollView( + child: Column( + children: [ + LoadingMyCourse(), + LoadingMyCourse(), + LoadingMyCourse(), + ], + ), + ); + } else if (state.searchResultState == SearchResultState.HasData) { + return ListView.builder( + shrinkWrap: true, + itemCount: state.searchResult!.data[0].length, + itemBuilder: (context, index) { + var myCourse = state.searchResult!.data[0][index]; + + return MyCourseList( + dataMyCourseModel: myCourse, + onDialogClose: refreshCourseList, + ); + }, + ); + } else if (state.searchResultState == SearchResultState.NoData) { + Provider.of<MyCourseProvider>(context, listen: false) + .clearSearch(); + return Center( + child: Text( + 'Kursus Tidak Ditemukan', + style: thirdTextStyle, + ), + ); + } else if (state.searchResultState == SearchResultState.Error) { + Provider.of<MyCourseProvider>(context, listen: false) + .clearSearch(); + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + return Center(child: Text('')); + } + }, + ), + ), + ); + } +} diff --git a/lib/screens/course/sertif.dart b/lib/screens/course/sertif.dart new file mode 100644 index 0000000..3683900 --- /dev/null +++ b/lib/screens/course/sertif.dart @@ -0,0 +1,784 @@ +import 'dart:async'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/services/all_certificate_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/certificate_provider.dart' + as certifProvider; +import 'package:qr_flutter/qr_flutter.dart'; + +class Sertif extends StatefulWidget { + Sertif({ + Key? key, + this.totalProgress, + this.idCourse, + this.idPayment, + this.finishDate, + this.text, + this.pdfBytes, + this.saving, + }) : super(key: key); + + static String routeName = "/sertif"; + final int? totalProgress; + final int? idCourse; + final String? idPayment; + final String? text; + final dynamic finishDate; + bool? saving = false; + final Uint8List? pdfBytes; + + @override + State<Sertif> createState() => _SertifState(); +} + +class _SertifState extends State<Sertif> { + final _globalKeySertif = GlobalKey(); + bool _isLoading = false; + + @override + Widget build(BuildContext context) { + Future.delayed(Duration(seconds: 0), () { + if (widget.text == null) { + Provider.of<certifProvider.CertificateProvider>(context, listen: false) + .getCertif(widget.idCourse.toString()); + } else { + Provider.of<certifProvider.CertificateProvider>(context, listen: false) + .checkSertif(widget.text!); + } + }); + + certifProvider.CertificateProvider? certifProv = + Provider.of<certifProvider.CertificateProvider>(context, listen: false); + + Widget notFinish() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + ), + body: Center( + child: Container( + height: getProportionateScreenHeight(270), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(15), + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.black + : Colors.grey, + blurRadius: 2, + spreadRadius: 1, + offset: Offset(0, 3)) + ]), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Sertifikat', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + letterSpacing: -0.5, + color: Theme.of(context).colorScheme.onBackground, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(28), + ), + ), + Text( + 'Belum Tersedia', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + letterSpacing: -0.5, + color: Theme.of(context).colorScheme.onBackground, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(28), + ), + ), + SizedBox(height: getProportionateScreenHeight(24)), + Padding( + padding: EdgeInsets.symmetric(horizontal: 15), + child: Stack( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenWidth(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey[300], + ), + ), + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(32)) * + (widget.totalProgress ?? 0) / + 100, + height: getProportionateScreenWidth(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor, + ), + child: Text( + '${widget.totalProgress ?? 0}%', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + color: Colors.white, + letterSpacing: 0.5, + ), + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Anda baru menyelesaikan ${widget.totalProgress ?? 0}% kursus', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: Theme.of(context).colorScheme.onBackground, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(13), + ), + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Anda belum memenuhi persyaratan untuk mendapatkan sertfikat. Selesaikan kursus anda untuk mendapatkan sertifikat penyelesaian kursus.', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: Theme.of(context).colorScheme.onBackground, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(13), + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget checkCertificateFinish() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + )), + body: Center( + child: Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + ), + child: Consumer<certifProvider.CertificateProvider>( + builder: (context, state, _) { + if (state.state == certifProvider.ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ); + } else if (state.state == + certifProvider.ResultState.HasData) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(15), + width: double.infinity, + height: 280, + child: LayoutBuilder( + builder: (context, constraint) { + return Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/images/certif_template_new.png'), + Positioned( + top: constraint.maxHeight / 2.55, + left: 0, + right: 0, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25), + child: Text( + certifProv.name!, + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Color(0xff575553)), + ), + ), + ), + Positioned( + top: constraint.maxHeight / 1.9, + left: 0, + right: 0, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 33), + child: Text('"${certifProv.title}"', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.bold, + fontSize: + (certifProv.title!.length > + 20) + ? 9 + : 12, + color: Color(0xff575553)), + textAlign: TextAlign.center), + ), + ), + Positioned( + top: constraint.maxHeight / 1.6, + left: 0, + right: 0, + child: Center( + child: Text( + '${DateFormat('dd MMMM yyyy ').format(DateTime.fromMillisecondsSinceEpoch(certifProv.finishDate * 1000))}', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.normal, + fontSize: 8, + color: Color(0xff575553)), + ), + ), + ), + Positioned( + bottom: constraint.maxHeight / 5.6, + left: constraint.maxWidth / 10, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Certificate no : ${certifProv.certificateNo}', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.normal, + fontSize: 4, + color: Color(0xff575553), + ), + ), + Text( + 'Certificate URL : https://vocasia-v4-develop.vercel.app/certificate/${certifProv.certificateNo}', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.normal, + fontSize: 4, + color: Color(0xff575553), + ), + ), + ], + ), + ) + ], + ); + }, + ), + ), + ], + ); + } else if (state.state == certifProvider.ResultState.Error) { + return Center( + child: Column( + children: [ + Text( + 'Terjadi Kesalahan Coba Lagi', + style: thirdTextStyle, + ), + ], + ), + ); + } else { + return Center( + child: Text( + 'Terjadi kesalahan', + style: thirdTextStyle, + ), + ); + } + }, + ), + ), + ), + ), + ); + } + + Widget checkCertificateNotFinished() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + body: Center( + child: Text( + 'Sertifikat Belum Diterbitkan', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: Theme.of(context).colorScheme.onBackground, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(20), + ), + ), + ), + ), + ); + } + + Widget buildCertificate() { + return Container( + color: Colors.red, + margin: EdgeInsets.all(15), + width: double.infinity, + height: 300, + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset('assets/certif_template_new.png'), + Positioned( + top: 120, + left: 0, + right: 0, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenHeight(25)), + child: Text( + 'Safinatun Najah Unju', + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + ), + Positioned( + top: 155, + left: 0, + right: 0, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenHeight(33)), + child: Column( + children: [ + Text( + '"Basic Excel Trainig For Professional Employees & Recruitment Selection Candidates"', + style: TextStyle( + fontWeight: FontWeight.w600, fontSize: 12), + textAlign: TextAlign.center), + SizedBox(height: getProportionateScreenHeight(10)), + Text( + 'Jakarta, 24 October 2021', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: getProportionateScreenWidth(8)), + ) + ], + ), + ), + ), + Positioned( + bottom: getProportionateScreenHeight(50), + left: (65 / 640) * MediaQuery.of(context).size.width, + child: Column( + children: [ + Text( + 'Certificate no : 257911829183', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: getProportionateScreenWidth(6)), + ), + Text( + 'Certificate no : 257911829183', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: getProportionateScreenWidth(6)), + ), + ], + ), + ) + ], + ), + ); + } + + Widget finish() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: Center( + child: Container( + child: Consumer<certifProvider.CertificateProvider>( + builder: (context, state, _) { + if (state.state == certifProvider.ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == + certifProvider.ResultState.HasData) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InteractiveViewer( + child: RepaintBoundary( + key: _globalKeySertif, + child: Container( + width: getProportionateScreenWidth(300), + height: getProportionateScreenHeight(173), + child: LayoutBuilder( + builder: (context, constraint) { + return Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/images/certif_template_new.png'), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.center, + widthFactor: 1.0, + heightFactor: 0.6, + child: Padding( + padding: EdgeInsets.only( + left: + + getProportionateScreenWidth(28.5), + right: + + getProportionateScreenWidth(240), + top: getProportionateScreenHeight(10), + bottom: getProportionateScreenHeight(90) + + ), + child: Text( + certifProv.certificateNo!.toUpperCase(), + textAlign: TextAlign.left, + style: primaryTextStyle.copyWith( + fontFamily: 'Arial', + fontWeight: FontWeight.w500, + fontSize: + getProportionateScreenWidth( + 4), + color: Color.fromARGB(255, 255, 255, 255), + ), + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.center, + widthFactor: 1.0, + heightFactor: 0.3, + child: Padding( + padding: EdgeInsets.only( + left: + getProportionateScreenWidth(27.5), + right: + getProportionateScreenHeight( + 80), + top: getProportionateScreenHeight( + 6), + ), + child: Text( + certifProv.name!.toUpperCase(), + textAlign: TextAlign.left, + style: primaryTextStyle.copyWith( + fontFamily: 'Arial', + fontWeight: FontWeight.bold, + fontSize: + getProportionateScreenWidth( + 9), + color: Color.fromARGB(255, 248, 124, 0), + ), + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomCenter, + widthFactor: 1.0, + heightFactor: 0.5, + child: Padding( + padding: EdgeInsets.only( + right: + getProportionateScreenWidth( + 85), + left: 27.5), + child: Text('"${certifProv.title}"', + style: primaryTextStyle.copyWith( + fontFamily: 'Arial', + fontWeight: FontWeight.bold, + fontSize: certifProv + .title!.length > + 40 + ? getProportionateScreenWidth( + 9) + : getProportionateScreenWidth( + 11), + color: Color.fromARGB(255, 248, 124, 0)), + textAlign: TextAlign.left, + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomLeft, + widthFactor: 0.299, + heightFactor: 0.62, + child: Center( + child: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight( + 2), + left: 5), + child: Text( + 'Jakarta, ${DateFormat('dd MMMM yyyy ').format(DateTime.fromMillisecondsSinceEpoch(certifProv.finishDate * 1000))}', + style: primaryTextStyle.copyWith( + fontWeight: + FontWeight.normal, + fontSize: + getProportionateScreenWidth( + 4), + color: Color.fromARGB(255, 0, 0, 0)), + ), + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomRight, + // widthFactor + heightFactor: 0.495, + child: Padding( + padding: EdgeInsets.only( + right: 19 + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + QrImageView( + data: "https://vocasia.id/?no-certificate=${certifProv.certificateNo}", + version: QrVersions.auto, + size: getProportionateScreenHeight(48), + gapless: false,), + ], + ), + ), + ), + ), + + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomRight, + // widthFactor + heightFactor: 0.18, + child: Padding( + padding: EdgeInsets.only( + right: 25 + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + RichText( + text: + TextSpan( + text: + 'Issued : ${DateFormat('dd MMMM yyyy ').format(DateTime.fromMillisecondsSinceEpoch(certifProv.finishDate * 1000))}', + style: primaryTextStyle + .copyWith( + fontWeight: + FontWeight.normal, + fontSize: + getProportionateScreenWidth( + 3), + color: + Color(0xff575553), + ), + ), + ) + ], + ), + ), + ), + ), + ], + ); + }, + ), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(70)), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + minimumSize: Size(double.infinity, + getProportionateScreenHeight(40)), + backgroundColor: primaryColor, + ), + onPressed: _isLoading + ? null + : () async { + setState(() { + _isLoading = true; + }); + try { + await AllCertificateServices() + .convertAndUpload(_globalKeySertif, + certifProv.idPayment!); + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Cek email anda!', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + color: baruTextutih), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5), + ), + ), + ); + } catch (e) { + print('Error: $e'); + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Gagal mengunduh sertifikat')), + ); + } finally { + setState(() { + _isLoading = false; + }); + } + }, + child: _isLoading + ? CircularProgressIndicator(color: baruTextutih) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + 'assets/icons/download.svg', + color: baruTextutih, + width: 18, + height: 18, + ), + Text( + 'Download Sertifikat', + style: thirdTextStyle.copyWith( + color: baruTextutih, + fontSize: + getProportionateScreenWidth(12), + ), + ), + ], + ), + ), + ) + ], + ); + } else if (state.state == certifProvider.ResultState.Error) { + return Center( + child: Column( + children: [ + Text( + 'Terjadi Kesalahan Coba Lagi', + style: thirdTextStyle, + ), + ], + ), + ); + } else { + return Center( + child: Text( + 'Terjadi kesalahan', + style: thirdTextStyle, + ), + ); + } + }, + ), + ), + ), + ), + ); + } + + if (widget.totalProgress == 100 && widget.idCourse != null) { + return finish(); + } else if (widget.totalProgress != 100 && widget.idCourse != null) { + return notFinish(); + } else if (widget.finishDate != false) { + return checkCertificateFinish(); + } else { + return checkCertificateNotFinished(); + } + } +} diff --git a/lib/screens/course/sertif_view.dart b/lib/screens/course/sertif_view.dart new file mode 100644 index 0000000..865c044 --- /dev/null +++ b/lib/screens/course/sertif_view.dart @@ -0,0 +1,676 @@ +import 'dart:async'; +import 'dart:ui'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:initial_folder/providers/certificate_provider.dart' + as certifProvider; + +class SertifView extends StatelessWidget { + final _globalKeySertif = GlobalKey(); + static String routeName = "/sertif"; + final int? totalProgress; + final int? idCourse; + final String? idPayment; + final String? text; + final dynamic finishDate; + + SertifView({ + Key? key, + this.totalProgress, + this.idCourse, + this.idPayment, + this.finishDate, + this.text, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Future.delayed(Duration(seconds: 0), () { + if (text == null) { + Provider.of<certifProvider.CertificateProvider>(context, listen: false) + .getCertif(idCourse.toString()); + } else { + Provider.of<certifProvider.CertificateProvider>(context, listen: false) + .checkSertif(text!); + } + }); + + certifProvider.CertificateProvider? certifProv = + Provider.of<certifProvider.CertificateProvider>(context, listen: false); + + Widget finish() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Sertifikat', + style: secondaryTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + body: Center( + child: Container( + child: Consumer<certifProvider.CertificateProvider>( + builder: (context, state, _) { + if (state.state == certifProvider.ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == + certifProvider.ResultState.HasData) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InteractiveViewer( + child: RepaintBoundary( + key: _globalKeySertif, + child: Container( + width: getProportionateScreenWidth(300), + height: getProportionateScreenHeight(173), + child: LayoutBuilder( + builder: (context, constraint) { + return Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/images/certif_template_new.png'), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.center, + widthFactor: 1.0, + heightFactor: 0.6, + child: Padding( + padding: EdgeInsets.only( + left: + + getProportionateScreenWidth(28.5), + right: + + getProportionateScreenWidth(240), + top: getProportionateScreenHeight(10), + bottom: getProportionateScreenHeight(90) + + ), + child: Text( + certifProv.certificateNo!.toUpperCase(), + textAlign: TextAlign.left, + style: primaryTextStyle.copyWith( + fontFamily: 'Arial', + fontWeight: FontWeight.w500, + fontSize: + getProportionateScreenWidth( + 4), + color: Color.fromARGB(255, 255, 255, 255), + ), + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.center, + widthFactor: 1.0, + heightFactor: 0.3, + child: Padding( + padding: EdgeInsets.only( + left: + getProportionateScreenWidth(27.5), + right: + getProportionateScreenHeight( + 80), + top: getProportionateScreenHeight( + 6), + ), + child: Text( + certifProv.name!.toUpperCase(), + textAlign: TextAlign.left, + style: primaryTextStyle.copyWith( + fontFamily: 'Arial', + fontWeight: FontWeight.bold, + fontSize: + getProportionateScreenWidth( + 9), + color: Color.fromARGB(255, 248, 124, 0), + ), + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomCenter, + widthFactor: 1.0, + heightFactor: 0.5, + child: Padding( + padding: EdgeInsets.only( + right: + getProportionateScreenWidth( + 85), + left: 27.5), + child: Text('"${certifProv.title}"', + style: primaryTextStyle.copyWith( + fontFamily: 'Arial', + fontWeight: FontWeight.bold, + fontSize: certifProv + .title!.length > + 40 + ? getProportionateScreenWidth( + 9) + : getProportionateScreenWidth( + 11), + color: Color.fromARGB(255, 248, 124, 0)), + textAlign: TextAlign.left, + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomLeft, + widthFactor: 0.299, + heightFactor: 0.62, + child: Center( + child: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight( + 2), + left: 5), + child: Text( + 'Jakarta, ${DateFormat('dd MMMM yyyy ').format(DateTime.fromMillisecondsSinceEpoch(certifProv.finishDate * 1000))}', + style: primaryTextStyle.copyWith( + fontWeight: + FontWeight.normal, + fontSize: + getProportionateScreenWidth( + 4), + color: Color.fromARGB(255, 0, 0, 0)), + ), + ), + ), + ), + ), + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomRight, + // widthFactor + heightFactor: 0.495, + child: Padding( + padding: EdgeInsets.only( + right: 19 + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + QrImageView( + data: "https://vocasia.id/?no-certificate=${certifProv.certificateNo}", + version: QrVersions.auto, + size: getProportionateScreenHeight(48), + gapless: false,), + ], + ), + ), + ), + ), + + + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomRight, + // widthFactor + heightFactor: 0.18, + child: Padding( + padding: EdgeInsets.only( + right: 25 + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + RichText( + text: + TextSpan( + text: + 'Issued : ${DateFormat('dd MMMM yyyy ').format(DateTime.fromMillisecondsSinceEpoch(certifProv.finishDate * 1000))}', + style: primaryTextStyle + .copyWith( + fontWeight: + FontWeight.normal, + fontSize: + getProportionateScreenWidth( + 3), + color: + Color(0xff575553), + ), + ), + ) + ], + ), + ), + ), + ), + ], + ); + }, + ), + ), + ), + ), + ], + ); + } else if (state.state == certifProvider.ResultState.Error) { + return Center( + child: Column( + children: [ + Text( + 'Terjadi Kesalahan Coba Lagi', + style: thirdTextStyle, + ), + ], + ), + ); + } else { + return Center( + child: Text( + 'Terjadi kesalahan', + style: thirdTextStyle, + ), + ); + } + }, + ), + ), + ), + ), + ); + } + + Widget notFinish() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + body: Center( + child: Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Sertifikat', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(28), + ), + ), + Text( + 'Belum Tersedia', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(28), + ), + ), + SizedBox(height: getProportionateScreenHeight(24)), + Stack( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenWidth(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white, + ), + ), + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(32)) * + (totalProgress ?? 0) / + 100, + height: getProportionateScreenWidth(20), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor, + ), + child: Text( + '${totalProgress ?? 0}%', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + color: baruTextutih, + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Anda baru menyelesaikan ${totalProgress ?? 0}% kursus', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(13), + ), + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Anda belum memenuhi persyaratan untuk mendapatkan sertfikat. Selesaikan kursus anda untuk mendapatkan sertifikat penyelesaian kursus.', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(13), + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget checkCertificateFinish() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + )), + body: Center( + child: Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + ), + child: Consumer<certifProvider.CertificateProvider>( + builder: (context, state, _) { + if (state.state == certifProvider.ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ); + } else if (state.state == + certifProvider.ResultState.HasData) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.all(15), + width: double.infinity, + height: 280, + child: LayoutBuilder( + builder: (context, constraint) { + return Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/images/certif_template_new.png'), + Positioned( + top: constraint.maxHeight / 2.55, + left: 0, + right: 0, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 25), + child: Text( + certifProv.name!, + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.bold, + fontSize: 14, + color: Color(0xff575553)), + ), + ), + ), + Positioned( + top: constraint.maxHeight / 1.9, + left: 0, + right: 0, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 33), + child: Text('"${certifProv.title}"', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.bold, + fontSize: + (certifProv.title!.length > + 20) + ? 9 + : 12, + color: Color(0xff575553)), + textAlign: TextAlign.center), + ), + ), + Positioned( + top: constraint.maxHeight / 1.6, + left: 0, + right: 0, + child: Center( + child: Text( + '${DateFormat('dd MMMM yyyy ').format(DateTime.fromMillisecondsSinceEpoch(certifProv.finishDate * 1000))}', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.normal, + fontSize: 8, + color: Color(0xff575553)), + ), + ), + ), + Positioned( + bottom: constraint.maxHeight / 5.6, + left: constraint.maxWidth / 10, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Certificate no : ${certifProv.certificateNo}', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.normal, + fontSize: 4, + color: Color(0xff575553), + ), + ), + Text( + 'Certificate URL : https://vocasia-v4-develop.vercel.app/certificate/${certifProv.idPayment}-VOCASIA-${certifProv.certificateNo}', + style: primaryTextStyle.copyWith( + fontWeight: FontWeight.normal, + fontSize: 4, + color: Color(0xff575553), + ), + ), + ], + ), + ) + ], + ); + }, + ), + ), + ], + ); + } else if (state.state == certifProvider.ResultState.Error) { + return Center( + child: Column( + children: [ + Text( + 'Terjadi Kesalahan Coba Lagi', + style: thirdTextStyle, + ), + ], + ), + ); + } else { + return Center( + child: Text( + 'Terjadi kesalahan', + style: thirdTextStyle, + ), + ); + } + }, + ), + ), + ), + ), + ); + } + + Widget checkCertificateNotFinished() { + return SafeArea( + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Sertifikat', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + body: Center( + child: Text( + 'Sertifikat Belum Diterbitkan', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(20), + ), + ), + ), + ), + ); + } + + Widget buildCertificate() { + return Container( + color: Colors.red, + margin: EdgeInsets.all(15), + width: double.infinity, + height: 300, + child: Stack( + alignment: Alignment.center, + children: [ + Image.asset('assets/certif_template_new.png'), + Positioned( + top: 120, + left: 0, + right: 0, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenHeight(25)), + child: Text( + 'Safinatun Najah Unju', + textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: getProportionateScreenWidth(14)), + ), + ), + ), + Positioned( + top: 155, + left: 0, + right: 0, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenHeight(33)), + child: Column( + children: [ + Text( + '"Basic Excel Trainig For Professional Employees & Recruitment Selection Candidates"', + style: TextStyle( + fontWeight: FontWeight.w600, fontSize: 12), + textAlign: TextAlign.center), + SizedBox(height: getProportionateScreenHeight(10)), + Text( + 'Jakarta, 24 October 2021', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: getProportionateScreenWidth(8)), + ) + ], + ), + ), + ), + Positioned( + bottom: getProportionateScreenHeight(50), + left: (65 / 640) * MediaQuery.of(context).size.width, + child: Column( + children: [ + Text( + 'Certificate no : 257911829183', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: getProportionateScreenWidth(6)), + ), + Text( + 'Certificate no : 257911829183', + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: getProportionateScreenWidth(6)), + ), + ], + ), + ) + ], + ), + ); + } + + if (totalProgress == 100 && idCourse != null) { + return finish(); + } else if (totalProgress != 100 && idCourse != null) { + return finish(); + } else if (finishDate != false) { + return checkCertificateFinish(); + } else { + return checkCertificateNotFinished(); + } + } +} \ No newline at end of file diff --git a/lib/screens/detail_course/components/aktifitas.dart b/lib/screens/detail_course/components/aktifitas.dart new file mode 100644 index 0000000..8a7e145 --- /dev/null +++ b/lib/screens/detail_course/components/aktifitas.dart @@ -0,0 +1,482 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/providers/section_lesson_course_provider.dart'; +import 'package:initial_folder/screens/course/component/expansion_tile_copy.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class Aktifitas extends StatefulWidget { + const Aktifitas({ + Key? key, + required this.id, + required this.totalDuration, + this.resoaktifitas, + }) : super(key: key); + + final String id; + final String totalDuration; + final String? resoaktifitas; + + @override + _AktifitasState createState() => _AktifitasState(); +} + +class _AktifitasState extends State<Aktifitas> { + final _tileKeys = []; + var _selectedIndex = -1; + final List<ValueNotifier<bool>> _isExpanded = []; + final ValueNotifier<int?> _selectedIndexNotifier = ValueNotifier<int?>(null); + + @override + void initState() { + super.initState(); + for (int i = 0; i < 10; i++) { + _isExpanded.add(ValueNotifier<bool>(false)); + } + } + + String formatDuration(String duration) { + var parts = duration.split(':'); + String formattedDuration = ''; + + if (parts[0] != '00') { + formattedDuration += '${parts[0]} jam '; + } + formattedDuration += + '${int.parse(parts[1])} menit ${int.parse(parts[2])} detik'; + + return '($formattedDuration)'; + } + + String formatDurations(String duration) { + if (duration == '') { + String formattedDuration = ''; + + print('masuk sini'); + formattedDuration += ''; + return formattedDuration; + } + List<String> parts = duration.split(':'); + int hours = int.parse(parts[0]); + int minutes = int.parse(parts[1]); + + String formattedDuration = ''; + + if (hours != 0) { + formattedDuration += '${hours}j'; + } + + if (minutes != 0) { + formattedDuration += '${int.parse(parts[1])}m'; + } + + formattedDuration += '${int.parse(parts[2])}d'; + + return formattedDuration; + } + + void resetExpansionTileKeysAndSelectedIndex() { + _tileKeys.clear(); + _selectedIndex = -1; + } + + @override + Widget build(BuildContext context) { + String _parseHtmlString(String htmlText) { + RegExp exp = RegExp(r"<[^>]*>| |&|"", + multiLine: true, caseSensitive: true); + return htmlText.replaceAll(exp, ''); + } + + resetExpansionTileKeysAndSelectedIndex(); + return Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(15), + right: getProportionateScreenWidth(15), + top: getProportionateScreenHeight(20), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Aktivitas', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox( + height: 8, + ), + Consumer<SectionLessonCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: Colors.amber, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + _isExpanded.clear(); + for (int i = 0; i < state.result!.data![0].length; i++) { + _isExpanded.add(ValueNotifier<bool>(false)); + } + return Container( + child: Column( + children: [ + Row( + children: [ + Text( + '${state.result!.data![0].length} Pelajaran', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + fontWeight: light, + ), + ), + SizedBox(width: getProportionateScreenWidth(6)), + Text( + formatDuration(widget.totalDuration == "" + ? "00:02:07" + : widget.totalDuration), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + fontWeight: light, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Container( + height: widget.resoaktifitas == null + ? getProportionateScreenHeight(195) + : getProportionateScreenHeight(195), + width: SizeConfig.screenWidth, + child: ListView.builder( + itemCount: state.result!.data![0].length, + itemBuilder: (context, index) { + var chapter = state.result!.data![0][index]; + final tileKey = GlobalKey(); + _tileKeys.add(tileKey); + return ValueListenableBuilder<bool>( + valueListenable: _isExpanded[index], + builder: (context, isExpanded, child) { + return Theme( + data: ThemeData.dark().copyWith( + colorScheme: ColorScheme.dark( + primary: secondaryColor), + dividerColor: Colors.transparent, + ), + child: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight(15)), + child: Column( + children: [ + ListTileTheme( + dense: true, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + borderRadius: + BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + color: baruTexthitam + .withOpacity(0.1), + blurRadius: 3, + spreadRadius: 1, + offset: Offset(0, 3), + ), + ], + ), + child: ExpansionTileCopy( + key: tileKey, + onExpansionChanged: + (bool expanding) { + if (expanding) { + if (_selectedIndexNotifier + .value != + null && + _selectedIndexNotifier + .value != + index && + _tileKeys[_selectedIndexNotifier + .value!] + .currentState != + null) { + _tileKeys[ + _selectedIndexNotifier + .value!] + .currentState! + .closeExpansion(); + } + _selectedIndexNotifier + .value = index; + } else if (_selectedIndexNotifier + .value == + index) { + _selectedIndexNotifier + .value = null; + } + }, + title: Text( + 'Bab ${index + 1}', + style: + thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: + getProportionateScreenWidth( + 11), + color: Theme.of(context) + .colorScheme + .onBackground, + ), + ), + subtitle: Html( + shrinkWrap: true, + data: chapter.title ?? '', + style: { + "body": Style( + margin: Margins.zero, + padding: + HtmlPaddings.zero, + fontSize: FontSize( + getProportionateScreenWidth( + 12)), + fontWeight: reguler, + fontFamily: 'Poppins', + color: Theme.of(context) + .colorScheme + .onBackground, + ), + }, + ), + trailing: + ValueListenableBuilder< + int?>( + valueListenable: + _selectedIndexNotifier, + builder: + (context, value, child) { + return Icon( + value == index + ? Icons.remove + : Icons.add, + color: primaryColor, + ); + }, + ), + children: chapter.dataLesson![0] + .asMap() + .entries + .map( + (e) => ListTile( + minVerticalPadding: 15, + leading: Text( + '${e.key + 1}', + style: thirdTextStyle + .copyWith( + color: Theme.of( + context) + .colorScheme + .onBackground, + ), + ), + minLeadingWidth: 15, + title: + Transform.translate( + offset: Offset(0, 0), + child: Text( + _parseHtmlString(e + .value + .titleLesson ?? + ''), + style: TextStyle( + fontSize: + getProportionateScreenWidth( + 12), + fontWeight: + semiBold, + fontFamily: + 'Poppins', + color: Theme.of( + context) + .colorScheme + .onBackground, + ), + ), + ), + subtitle: + Transform.translate( + offset: Offset(0, 0), + child: Text( + (e.value.lessonType == + 'video') + ? 'Video - ${formatDuration(e.value.duration.toString())}' + : (e.value.lessonType == + 'quiz') + ? 'Quiz' + : (e.value + .attachment + .toString() + .contains('.pdf')) + ? 'PDFs' + : (e.value.attachment.toString().contains('.pptx')) + ? 'PPT' + : (e.value.attachment.toString().contains('.rar')) + ? 'RAR' + : (e.value.attachment.toString().contains('.zip')) + ? 'ZIP' + : (e.value.attachment.toString().contains('.xlsx')) + ? 'Excel' + : (e.value.attachment.toString().contains('.jpg') || e.value.attachment.toString().contains('.jpeg') || e.value.attachment.toString().contains('.png')) + ? 'Image' + : (e.value.attachment.toString().contains('.docx')) + ? 'Document' + : (e.value.attachment.toString().contains('.txt')) + ? 'Text' + : 'Terjadi Kesalahan', + style: + thirdTextStyle + .copyWith( + color: Theme.of( + context) + .colorScheme + .onBackground, + fontSize: + getProportionateScreenHeight( + 8), + ), + ), + ), + trailing: (e.value + .lessonType == + 'video') + ? Image.asset( + 'assets/images/play_button_new.png', + color: Theme.of( + context) + .colorScheme + .onBackground, + scale: 3, + ) + : (e.value.lessonType == + 'quiz') + ? Image.asset( + 'assets/icons/lms/ListNumbers.png', + color: Theme.of( + context) + .colorScheme + .onBackground, + scale: + getProportionateScreenWidth( + 1.3), + ) + : (e.value + .attachment + .toString() + .contains( + '.pdf')) + ? Image + .asset( + 'assets/icons/lms/FilePdf.png', + color: Theme.of(context) + .colorScheme + .onBackground, + scale: getProportionateScreenWidth( + 1.3), + ) + : (e.value + .attachment + .toString() + .contains( + '.rar')) + ? Image + .asset( + 'assets/icons/lms/FileArchive.png', + color: + Theme.of(context).colorScheme.onBackground, + scale: + getProportionateScreenWidth(1.3), + ) + : (e.value.attachment.toString().contains('.zip')) + ? Image.asset( + 'assets/icons/lms/FileZip.png', + color: Theme.of(context).colorScheme.onBackground, + scale: getProportionateScreenWidth(1.3), + ) + : (e.value.attachment.toString().contains('.pptx')) + ? Image.asset( + 'assets/icons/lms/FilePpt.png', + color: Theme.of(context).colorScheme.onBackground, + scale: getProportionateScreenWidth(1.3), + ) + : (e.value.attachment.toString().contains('.xlsx')) + ? Image.asset( + 'assets/icons/lms/FileXls.png', + color: Theme.of(context).colorScheme.onBackground, + scale: getProportionateScreenWidth(1.3), + ) + : (e.value.attachment.toString().contains('.jpg') || e.value.attachment.toString().contains('.jpeg') || e.value.attachment.toString().contains('.png')) + ? Image.asset( + 'assets/icons/lms/FileImage.png', + color: Theme.of(context).colorScheme.onBackground, + scale: getProportionateScreenWidth(1.3), + ) + : (e.value.attachment.toString().contains('.txt')) + ? Image.asset( + 'assets/icons/lms/FileText.png', + color: Theme.of(context).colorScheme.onBackground, + scale: getProportionateScreenWidth(1.3), + ) + : ((e.value.attachment.toString().contains('.docx'))) + ? Image.asset( + 'assets/icons/lms/FileDoc.png', + color: Theme.of(context).colorScheme.onBackground, + scale: getProportionateScreenWidth(1.3), + ) + : SizedBox(), + ), + ) + .toList(), + ), + ), + ), + ], + ), + )); + }, + ); + }, + ), + ) + ], + ), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Center( + child: Padding( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(30)), + child: Text( + 'Kursus ini belum memiliki aktivitas', + style: thirdTextStyle.copyWith(), + ), + ), + ); + } else { + return Center(child: Text('')); + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/app_bar.dart b/lib/screens/detail_course/components/app_bar.dart new file mode 100644 index 0000000..51d2989 --- /dev/null +++ b/lib/screens/detail_course/components/app_bar.dart @@ -0,0 +1,195 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/notification_provider.dart'; +import 'package:initial_folder/screens/home/components/appBar/icon_btn_with_counter.dart'; +import 'package:initial_folder/screens/home/components/notification.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; + +class AppBarHeader extends StatelessWidget { + const AppBarHeader({ + Key? key, + this.idcourse, + this.color, + }) : super(key: key); + + final String? idcourse; + final Color? color; + + @override + Widget build(BuildContext context) { + Future _showDialogNotLogin(String teks) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum $teks', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + }, + child: Text( + 'Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor, + ), + ), + ), + ], + ), + ); + } + + handleNotLoginCart() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: CartPage(), + ), + ); + } else { + String teks = 'dapat mengakses keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginNotif() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: Notifikasi(), + ), + ); + } else { + String teks = 'dapat mengakses notifikasi'; + return _showDialogNotLogin(teks); + } + } + + return AnimatedContainer( + duration: Duration(milliseconds: 500), + curve: Curves.ease, + color: color, + width: SizeConfig.screenWidth, + height: getProportionateScreenHeight(40), + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10), + left: getProportionateScreenWidth(5), + ), + child: Row( + children: [ + IconButton( + alignment: Alignment.centerLeft, + icon: Theme.of(context).brightness == Brightness.dark + ? SvgPicture.asset('assets/icons/arrow_back_dark.svg') + : SvgPicture.asset('assets/icons/arrow_back.svg'), + onPressed: () { + Navigator.pop(context); + }, + ), + Spacer(), + Transform.scale( + origin: Offset(-11, 0), + scale: getProportionateScreenHeight(1), + child: Container( + padding: EdgeInsets.fromLTRB(getProportionateScreenHeight(3), 0, + getProportionateScreenHeight(4), 0), + child: Consumer<NotificationProvider>( + builder: (context, value, child) { + return IconBtnWithCounter( + icon: Theme.of(context).brightness == Brightness.dark + ? SvgPicture.asset( + "assets/icons/notification_dark.svg") + : SvgPicture.asset("assets/icons/notification.svg"), + numOfitem: + (Condition.loginEmail || Condition.loginFirebase) + ? value.notificationCount + : 0, + press: () { + handleNotLoginNotif(); + }, + ); + }, + ), + ), + ), + !Condition.loginEmail && !Condition.loginFirebase + ? Transform.scale( + origin: Offset(0, 0), + scale: getProportionateScreenHeight(1), + child: Container( + padding: EdgeInsets.fromLTRB( + getProportionateScreenHeight(3), + 0, + getProportionateScreenHeight(3), + 0), + child: IconBtnWithCounter( + numOfitem: 0, + icon: Theme.of(context).brightness == Brightness.dark + ? SvgPicture.asset("assets/icons/cart_dark.svg") + : SvgPicture.asset("assets/icons/cart.svg"), + press: () => handleNotLoginCart(), + ), + ), + ) + : Transform.scale( + origin: Offset(0, 0), + scale: getProportionateScreenHeight(1), + child: Container( + padding: EdgeInsets.fromLTRB( + getProportionateScreenHeight(3), + 0, + getProportionateScreenHeight(3), + 0), + child: Consumer<CartsProvider>( + builder: (context, state, _) { + return IconBtnWithCounter( + numOfitem: state.result == null ? 0 : state.lenght, + icon: Theme.of(context).brightness == + Brightness.dark + ? SvgPicture.asset("assets/icons/cart_dark.svg") + : SvgPicture.asset("assets/icons/cart.svg"), + press: () => handleNotLoginCart(), + ); + }, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/detail_course/components/app_bar_filter.dart b/lib/screens/detail_course/components/app_bar_filter.dart new file mode 100644 index 0000000..92b29d8 --- /dev/null +++ b/lib/screens/detail_course/components/app_bar_filter.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/screens/home/components/notification.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/search_course/component/filter.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; + +class AppBarFilter extends StatelessWidget { + const AppBarFilter({ + Key? key, + this.idcourse, + }) : super(key: key); + + final String? idcourse; + + @override + Widget build(BuildContext context) { + Future _showDialogNotLogin(String teks) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum $teks', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + }, + child: Text('Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ], + ), + ); + } + + handleNotLoginCart() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Navigator.of(context).push( + CustomNavigator( + child: CartPage(), + ), + ); + } else { + String teks = 'dapat mengakses keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginNotif() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Navigator.push( + context, + CustomNavigator( + child: Notifikasi(), + ), + ); + } else { + String teks = 'dapat mengakses notifikasi'; + return _showDialogNotLogin(teks); + } + } + + return Container( + margin: EdgeInsets.only(left: getProportionateScreenWidth(7)), + width: SizeConfig.screenWidth, + height: getProportionateScreenHeight(40), + child: Row( + children: [ + IconButton( + alignment: Alignment.centerLeft, + icon: Icon( + Icons.arrow_back, + size: getProportionateScreenHeight(18), + color: Theme.of(context).brightness == Brightness.dark + ? tenthColor + : ninthColor, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + Spacer(), + // IconButton( + // padding: EdgeInsets.zero, + // onPressed: () => Navigator.of(context, rootNavigator: true).push( + // CustomNavigator( + // child: const Filter(isNotSearch: true), + // ), + // ), + // icon: Icon(Icons.tune_rounded, color: primaryColor), + // ), + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/custom_tab_bar.dart b/lib/screens/detail_course/components/custom_tab_bar.dart new file mode 100644 index 0000000..3bf1494 --- /dev/null +++ b/lib/screens/detail_course/components/custom_tab_bar.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/tab_provider.dart'; +import 'package:initial_folder/screens/detail_course/components/aktifitas.dart'; +import 'package:initial_folder/screens/detail_course/components/deskripsi.dart'; +import 'package:initial_folder/screens/detail_course/components/tab_bar_items.dart'; +import 'package:initial_folder/screens/detail_course/components/terkait.dart'; +import 'package:initial_folder/screens/detail_course/components/ulasan.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:provider/provider.dart'; + +class CustomTabBar extends StatefulWidget { + const CustomTabBar({ + Key? key, + required this.dataDetailCourseModel, + this.totalDuration, + this.bio, + this.instructor, + this.rating, + this.review, + this.totalLesson, + this.totalStudent, + this.fotoProfile, + this.headline, + this.resoaktifitas, + this.idCategory, + }) : super(key: key); + final DataDetailCourseModel dataDetailCourseModel; + final String? totalDuration, + bio, + instructor, + review, + totalLesson, + totalStudent, + fotoProfile, + rating, + headline, + resoaktifitas, + idCategory; + + @override + State<CustomTabBar> createState() => _CustomTabBarState(); +} + +class _CustomTabBarState extends State<CustomTabBar> { + @override + Widget build(BuildContext context) { + TabProvider tab = Provider.of<TabProvider>(context); + + void _onHorizontalDragEnd(DragEndDetails details) { + double velocity = details.primaryVelocity ?? 0.0; + if (velocity < 0) { + if (tab.currentIndex < 3) { + tab.currentIndex++; + } + } else if (velocity > 0) { + if (tab.currentIndex > 0) { + tab.currentIndex--; + } + } + } + + Widget buildContent(int currentIndex) { + switch (currentIndex) { + case 0: + return Desksripsi( + dataDetailCourseModel: widget.dataDetailCourseModel, + video: true, + id: widget.dataDetailCourseModel.id, + instructor: widget.instructor, + bio: widget.bio, + rating: widget.rating, + fotoProfile: widget.fotoProfile, + review: widget.review, + totalLesson: widget.totalLesson, + totalStudent: widget.totalStudent, + headline: widget.headline, + ); + case 1: + return Aktifitas( + id: widget.dataDetailCourseModel.id, + totalDuration: widget.totalDuration ?? '', + resoaktifitas: widget.resoaktifitas, + ); + case 2: + return Ulasan( + id: widget.dataDetailCourseModel.id, + ); + + case 3: + return Terkait( + idCategory: widget.idCategory, + ); + default: + return Desksripsi( + dataDetailCourseModel: widget.dataDetailCourseModel, + video: true, + id: widget.dataDetailCourseModel.id, + instructor: widget.instructor, + bio: widget.bio, + rating: widget.rating, + fotoProfile: widget.fotoProfile, + review: widget.review, + totalLesson: widget.totalLesson, + totalStudent: widget.totalStudent, + headline: widget.headline, + ); + } + } + + return Column( + children: [ + Container( + width: SizeConfig.screenWidth, + height: getProportionateScreenHeight(29), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: -12, + blurRadius: 8, + offset: Offset(0, getProportionateScreenHeight(12)), + ), + ], + ), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + GestureDetector( + onTap: () => tab.currentIndex = 0, + child: TabBarItems( + index: 0, + title: 'Deskripsi', + ), + ), + GestureDetector( + onTap: () => tab.currentIndex = 1, + child: TabBarItems( + index: 1, + title: 'Aktivitas', + ), + ), + GestureDetector( + onTap: () => tab.currentIndex = 2, + child: TabBarItems( + index: 2, + title: 'Ulasan', + ), + ), + GestureDetector( + onTap: () => tab.currentIndex = 3, + child: TabBarItems( + index: 3, + title: 'Terkait', + ), + ), + ], + ), + ), + ), + GestureDetector( + onHorizontalDragEnd: _onHorizontalDragEnd, + child: buildContent(tab.currentIndex), + ), + ], + ); + } +} diff --git a/lib/screens/detail_course/components/deskripsi.dart b/lib/screens/detail_course/components/deskripsi.dart new file mode 100644 index 0000000..d3301f0 --- /dev/null +++ b/lib/screens/detail_course/components/deskripsi.dart @@ -0,0 +1,557 @@ +import 'dart:convert'; +import 'package:expandable/expandable.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/description_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/detail_course/components/kursus_include_item.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class Desksripsi extends StatefulWidget { + const Desksripsi({ + Key? key, + required this.dataDetailCourseModel, + required this.id, + this.instructor, + this.bio, + this.rating, + this.review, + this.totalStudent, + this.totalLesson, + this.video = false, + this.fotoProfile, + this.headline, + }) : super(key: key); + + final DataDetailCourseModel dataDetailCourseModel; + final String id; + final String? instructor; + final String? bio; + final String? rating; + final String? review; + final String? fotoProfile; + final String? totalLesson; + final String? totalStudent; + final String? headline; + final bool video; + + @override + State<Desksripsi> createState() => _DesksripsiState(); +} + +class _DesksripsiState extends State<Desksripsi> { + bool isExpanded = false; + bool isExpanded2 = false; + bool isExpanded3 = false; + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + DescriptionProvider descriptionProvider = + Provider.of<DescriptionProvider>(context); + final bool hasDescription = + widget.dataDetailCourseModel.description!.isNotEmpty; + String formatDuration(String duration) { + var parts = duration.split(':'); + return '${int.parse(parts[0]).toString()} jam ${int.parse(parts[1]).toString()} menit ${int.parse(parts[2]).toString()} detik'; + } + + List outcomes; + try { + outcomes = jsonDecode(widget.dataDetailCourseModel.outcome ?? '[]'); + } catch (e) { + outcomes = []; + } + List requirement; + try { + requirement = + jsonDecode(widget.dataDetailCourseModel.requirement ?? '[]'); + } catch (e) { + requirement = []; + } + + Widget kemampuanDiraih(String title, + {bool showToggle = false, bool isLast = false}) { + return Container( + margin: EdgeInsets.only(bottom: 6), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.isNotEmpty + ? Icon( + Icons.check, + size: getProportionateScreenHeight(13), + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ) + : SizedBox.shrink(), + SizedBox(width: getProportionateScreenWidth(9)), + Flexible( + child: RichText( + text: TextSpan( + text: title, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: light, + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : baruTexthitam, + ), + children: showToggle || isLast + ? [ + TextSpan( + text: + isExpanded ? ' Lihat Sedikit' : ' Lihat Semua', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + setState(() { + isExpanded = !isExpanded; + }); + }, + ), + ] + : [], + ), + ), + ), + ], + ), + ); + } + + Widget persyaratan(String title, + {bool showToggle = false, bool isLast = false}) { + return Container( + margin: EdgeInsets.only(bottom: 6), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.isNotEmpty + ? Icon( + Icons.brightness_1, + size: getProportionateScreenHeight(13), + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ) + : SizedBox.shrink(), + SizedBox(width: getProportionateScreenWidth(9)), + Flexible( + child: RichText( + text: TextSpan( + text: title, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: light, + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : baruTexthitam, + ), + children: showToggle || isLast + ? [ + TextSpan( + text: + isExpanded3 ? ' Lihat Sedikit' : ' Lihat Semua', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : fourthColor, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + setState(() { + isExpanded3 = !isExpanded3; + }); + }, + ), + ] + : [], + ), + ), + ), + ], + ), + ); + } + + List<Widget> kemampuanDiraihList() { + List<Widget> items = outcomes + .asMap() + .entries + .map((entry) => kemampuanDiraih(entry.value, + showToggle: !isExpanded && entry.key == 2 && outcomes.length > 3, + isLast: entry.key == outcomes.length - 1 && outcomes.length > 3)) + .toList(); + + if (!isExpanded && outcomes.length > 3) { + items = items.take(3).toList(); + items.add(kemampuanDiraih("", isLast: false, showToggle: false)); + } + + return items; + } + + List<Widget> persyaratanList() { + List<Widget> items = requirement + .asMap() + .entries + .map((entry) => persyaratan(entry.value, + showToggle: + !isExpanded3 && entry.key == 2 && requirement.length > 3, + isLast: entry.key == requirement.length - 1 && + requirement.length > 3)) + .toList(); + + if (!isExpanded3 && requirement.length > 3) { + items = items.take(3).toList(); + items.add(persyaratan("", isLast: false, showToggle: false)); + } + + return items; + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(9)), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + top: getProportionateScreenHeight(10), + ), + child: Text( + 'Kursus Ini Sudah Termasuk', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + SizedBox(height: 10), + KursusIncludeItems( + svg: 'assets/icons/clock.svg', + text: + '${formatDuration(widget.dataDetailCourseModel.totalDuration!)} video pembelajaran'), + KursusIncludeItems( + svg: 'assets/icons/lesson.svg', + text: '${widget.dataDetailCourseModel.totalLesson} pelajaran'), + KursusIncludeItems( + svg: 'assets/icons/calendar.svg', + text: 'Akses full seumur hidup'), + Padding( + padding: EdgeInsets.only(left: getProportionateScreenWidth(1)), + child: KursusIncludeItems( + svg: 'assets/icons/phone.svg', + text: ' Akses di ponsel dan TV '), + ), + SizedBox(height: 10), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Column( + children: [ + ExpandableNotifier( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(106), + bottom: getProportionateScreenWidth(8), + ), + child: Text( + 'Kemampuan Yang Akan Diraih', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(10), + bottom: widget.dataDetailCourseModel.outcome == "[]" || + widget.dataDetailCourseModel.outcome!.isEmpty + ? getProportionateScreenHeight(20) + : getProportionateScreenHeight(0), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: kemampuanDiraihList(), + ), + ), + ], + ), + ), + ], + ), + Column( + children: [ + ExpandableNotifier( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(106), + bottom: getProportionateScreenWidth(8), + ), + child: Text( + 'Persyaratan', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(10), + bottom: widget.dataDetailCourseModel.requirement == + "[]" || + widget.dataDetailCourseModel.requirement!.isEmpty + ? getProportionateScreenHeight(20) + : getProportionateScreenHeight(0), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: persyaratanList(), + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Column( + children: [ + isExpanded + ? SizedBox(height: getProportionateScreenHeight(12)) + : SizedBox.shrink(), + Align( + alignment: Alignment.topLeft, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Text( + 'Deskripsi', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(5)), + child: Container( + height: descriptionProvider.isExpanded + ? getProportionateScreenHeight(53) + : null, + child: Html( + data: widget.dataDetailCourseModel.description ?? "", + style: { + "p": Style( + fontSize: FontSize(getProportionateScreenWidth(11)), + fontWeight: light, + margin: Margins.only(top: 0, bottom: 0), + fontFamily: 'Poppins', + color: Theme.of(context).colorScheme.onBackground), + }, + ), + ), + ), + if (hasDescription && + widget.dataDetailCourseModel.description!.length > 120) + Container( + child: Align( + alignment: Alignment.topLeft, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: RichText( + text: TextSpan( + text: descriptionProvider.isExpanded + ? ' Lihat Semua' + : ' Lihat Sedikit', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : fourthColor, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + setState(() { + descriptionProvider.isExpanded = + !descriptionProvider.isExpanded; + }); + }, + ), + ), + ), + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (hasDescription && + widget.dataDetailCourseModel.description!.length > 120) + SizedBox(height: getProportionateScreenHeight(10)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Row( + children: [ + CircleAvatar( + radius: 20, + backgroundColor: primaryColor, + backgroundImage: widget.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage(widget.fotoProfile!) as ImageProvider, + ), + SizedBox(width: getProportionateScreenWidth(10)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.instructor ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: bold, + ), + ), + Row( + children: [ + Text( + 'Instructor, ', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ), + Text( + '${widget.totalStudent ?? '0'} Murid, ', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ), + Text( + '${widget.totalLesson ?? ''} Kursus', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: light, + ), + ) + ], + ), + ], + ), + ], + ), + ), + if (widget.bio == null || widget.bio!.isEmpty) + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenHeight(10), + right: getProportionateScreenHeight(10), + bottom: getProportionateScreenHeight(10), + ), + child: const SizedBox(height: 5) + ) + else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(5), + right: getProportionateScreenWidth(10), + ), + child: isExpanded2 + ? Html( + data: widget.bio, + style: { + "body": Style( + fontSize: + FontSize(getProportionateScreenWidth(11)), + fontWeight: light, + fontFamily: 'Poppins', + ), + }, + ) + : Html( + data: widget.bio != null && widget.bio!.length > 100 + ? widget.bio!.substring(0, 100) + : widget.bio!, + style: { + "body": Style( + fontSize: + FontSize(getProportionateScreenWidth(11)), + fontWeight: light, + fontFamily: 'Poppins', + ), + }, + ), + ), + if (widget.bio!.isNotEmpty && widget.bio!.length > 100) + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(12), + bottom: getProportionateScreenHeight(10), + ), + child: GestureDetector( + onTap: () { + setState(() { + isExpanded2 = !isExpanded2; + }); + }, + child: Text( + isExpanded2 ? 'Lihat Sedikit' : 'Lihat Semua', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + color: + Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : fourthColor, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ], + ), + ], + ), + ], + ); + } +} diff --git a/lib/screens/detail_course/components/detail_list_ulasan.dart b/lib/screens/detail_course/components/detail_list_ulasan.dart new file mode 100644 index 0000000..ec279ea --- /dev/null +++ b/lib/screens/detail_course/components/detail_list_ulasan.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/helper/validator.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class DetailListUlasan extends StatelessWidget { + const DetailListUlasan({ + Key? key, + required this.name, + required this.starRating, + required this.review, + required this.date, + }) : super(key: key); + final String name; + final double starRating; + final String review; + final String date; + @override + Widget build(BuildContext context) { + return Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(5)), + Text( + name, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + RatingBarIndicator( + itemSize: getProportionateScreenWidth(11), + rating: starRating, + direction: Axis.horizontal, + itemCount: 5, + itemPadding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(1)), + //itemPadding: EdgeInsets.symmetric(horizontal: 4.0), + itemBuilder: (context, _) => + FaIcon(FontAwesomeIcons.solidStar, color: thirteenColor), + ), + SizedBox(width: getProportionateScreenWidth(5)), + Text( + dateFormatUlasan(date), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + ), + ) + ], + ), + Container( + child: Text( + review, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(7)), + Divider( + color: Color(0xff2D2D2D), + thickness: 1, + ), + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/header.dart b/lib/screens/detail_course/components/header.dart new file mode 100644 index 0000000..29e65f7 --- /dev/null +++ b/lib/screens/detail_course/components/header.dart @@ -0,0 +1,1535 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/models/voucher_model.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/radeem_voucher_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/checkout/checkout_detail_coupon.dart'; +import 'package:initial_folder/screens/checkout/components/field_kupon.dart'; +import 'package:initial_folder/screens/detail_course/components/murid_and_rating.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/my_course/success_free_course.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/services/user_info_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; +import 'package:initial_folder/providers/payments_provider.dart' as paysProv; +import 'package:initial_folder/providers/detail_course_provider.dart' + as detailCourseProv; +import 'package:initial_folder/providers/whislist_provider.dart' + as wishlistProvider; + +class Header extends StatefulWidget { + const Header( + {Key? key, + required this.dataDetailCourseModel, + this.coupon, + this.discountPrice, + this.price, + this.idCourse}) + : super(key: key); + + final DataDetailCourseModel dataDetailCourseModel; + final String? coupon; + final String? discountPrice; + final String? price; + final String? idCourse; + + @override + State<Header> createState() => _HeaderState(); +} + +class _HeaderState extends State<Header> { + @override + bool isAdmin = false; + int? iDuser; + String? tokenCek; + + void cekadmin() { + UserInfoService().getDataDiriADMIN().then((value) => { + if (value == 401) + { + setState(() { + isAdmin = true; + }) + } + }); + } + + void getIDuser() { + UsersInfo().getIdUser().then((value) => { + setState(() { + iDuser = value; + }), + }); + } + + void getTokenuser() { + UsersInfo().getToken().then((value) => { + setState(() { + tokenCek = value; + }), + }); + } + + @override + void initState() { + super.initState(); + cekadmin(); + getIDuser(); + getTokenuser(); + } + + Widget build(BuildContext context) { + final selected = Provider.of<TotalPriceProvider>(context); + final kuponController = TextEditingController(); + final themeProvider = Provider.of<ThemeProvider>(context); + final finalPriceCoupon = + Provider.of<TotalPriceProvider>(context).finalPriceCoupon; + String? kuponErrorMessage = ""; + bool isLoading = false; + WishlistPostProvider wishlistPostProvider = + Provider.of<WishlistPostProvider>(context); + paysProv.PaymentsProvider pay = + Provider.of<paysProv.PaymentsProvider>(context); + + showNotifDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Consumer<paysProv.PaymentsProvider>( + builder: (context, state, child) { + if (state.state == paysProv.ResultState.gagal) { + return Container( + height: getProportionateScreenHeight(40), + width: getProportionateScreenWidth(15), + child: Center( + child: Text( + 'Anda sudah memiliki kursus ini', + style: primaryTextStyle.copyWith(fontSize: 12), + textAlign: TextAlign.center, + ), + ), + ); + } else { + Future.delayed(Duration.zero, () { + Navigator.pop(context); + CherryToast.error( + animationDuration: Durations.long1, + title: Text("Terjadi Kesalahan, silahkan coba lagi", + style: TextStyle( + color: Colors.black, + fontSize: 15, + )), + animationType: AnimationType.fromTop, + ).show(context); + }); + return Container(); + } + }, + ), + ); + }, + ); + } + + Future _showDialogNotLogin() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum menambahkan ke wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () => Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false), + child: Text( + 'Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + ], + ), + ); + } + + Future _showDialogNotLoginKupon() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum tukar kupon', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () => Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false), + child: Text( + 'Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor, + ), + ), + ), + ], + ), + ); + } + + updateWishlist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .deleteWishlist(int.parse(widget.dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + } + } + + addWishlistNotExist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .addWishlist(int.parse(widget.dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist() + .then( + (value) => { + if (value == "gagal") + { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Terjadi kesalahan, silahkan coba lagi", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + } + }, + ); + } + } + + checkUser() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + updateWishlist(); + } else { + return _showDialogNotLogin(); + } + } + + wishlistExist() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + addWishlistNotExist(); + } else { + return _showDialogNotLogin(); + } + } + + Future _showKupon() { + setState(() { + kuponErrorMessage = ""; + }); + return showModalBottomSheet( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + elevation: 0.0, + context: context, + isScrollControlled: true, + builder: (context) { + return Padding( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + child: SingleChildScrollView( + child: Container( + color: Theme.of(context).colorScheme.background, + padding: EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + Center( + child: Column( + children: [ + Text( + 'Tukarkan Voucher', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + SizedBox(height: getProportionateScreenHeight(16)), + Text( + 'Masukkan kode kupon untuk klaim\npromo menarik Vocasia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12)), + ), + SizedBox(height: getProportionateScreenHeight(30)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: FieldKupon( + controler: kuponController, + ), + ), + SizedBox(height: getProportionateScreenHeight(7)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: isLoading + ? LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: Color(0xff050505), + ) + : TapDebouncer( + cooldown: + const Duration(milliseconds: 3000), + onTap: () async { + final voucher = kuponController.text; + var radeemVoucherProvider = + Provider.of<RadeemVoucherProvider>( + context, + listen: false); + VoucherModel? response = + await radeemVoucherProvider + .radeemVoucher( + int.parse(widget + .dataDetailCourseModel + .id), + voucher); + if (response?.error == false) { + await Provider.of<CartsProvider>( + context, + listen: false) + .getCarts(); + VoucherModel? detailKupon = + radeemVoucherProvider.result; + DataVoucher? firstDataItem = + detailKupon?.data?.first; + Provider.of<OrderProvider>(context, + listen: false) + .clear(); + var finalPriceReal = firstDataItem + ?.finalPrice + .toString() + .replaceAll(".", ""); + int? discountPriceRounded = + firstDataItem?.discountPrice + ?.round() ?? + 0; + if (firstDataItem?.typeCoupon != "1" || + firstDataItem?.finalPrice == 0 || + firstDataItem + ?.finalPrice.toString == + "0") + Provider.of<OrderProvider>(context, + listen: false) + .addOrder( + id: firstDataItem?.idCourse, + title: firstDataItem?.courseName, + price: finalPriceReal, + discountPrice: finalPriceReal, + imageUrl: firstDataItem + ?.thubmnail == + null + ? '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg' + : firstDataItem?.thubmnail, + instructor: + firstDataItem?.instructor, + ); + selected.selectedPriceCoupon = + int.parse( + firstDataItem?.originalPrice ?? + "0"); + selected.selectedTypeCoupon = + firstDataItem?.typeCoupon ?? ""; + selected.selectedCouponText = + voucher.toString(); + selected.selectedFinalPriceCoupon = + int.parse(firstDataItem!.finalPrice + .toString()); + selected.selectedTotalPrice = int.parse( + firstDataItem.finalPrice + .toString()); + selected.selectedTotalPrices = + int.parse(firstDataItem + .originalPrice + .toString()); + selected.selectedPotonganKupon = + discountPriceRounded!; + Provider.of<OrderProvider>(context, + listen: false) + .getTotalPrice(firstDataItem + .finalPrice + .toString()); + int? potonganKupon = (firstDataItem + .discountPrice != + null && + firstDataItem.finalPrice != null + ? discountPriceRounded - + firstDataItem.finalPrice + : null) as int?; + int? potonganKupon2 = + (firstDataItem.originalPrice != + null && + firstDataItem.finalPrice != + null) + ? int.parse(firstDataItem + .originalPrice ?? + "0") - + int.parse(firstDataItem + .finalPrice + .toString()) + : null; + selected.selectedPenguranganHarga = + potonganKupon2!; + if (firstDataItem.typeCoupon != "1") + Navigator.of(context, + rootNavigator: true) + .pushReplacement( + MaterialPageRoute( + builder: (context) => + CheckoutDetailCoupon( + idCart: [], + potonganKupon: discountPriceRounded != + 0 + ? (discountPriceRounded > + int.parse( + firstDataItem + .finalPrice + .toString()) + ? potonganKupon + : discountPriceRounded) + : potonganKupon2, + discountHarga: + firstDataItem.finalPrice, + isKupon: true, + ), + ), + ); + var token = + await UsersInfo().getToken(); + if (firstDataItem.typeCoupon == + "1") if (token != + null || + Condition.loginFirebase == true) { + if (await pay.freeCourseCoupon( + int.parse( + firstDataItem.idCourse!), + voucher)) { + await Provider.of<MyCourseProvider>( + context, + listen: false) + .getMyCourse(); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + SuccessFreeCourse( + id: firstDataItem.idCourse, + thumbnail: + firstDataItem.thubmnail, + title: + firstDataItem.courseName, + instructor: + firstDataItem.instructor, + ), + ), + ); + } else { + showNotifDialog(context); + } + } else { + String teks = 'memiliki kursus ini'; + return _showDialogNotLogin(); + } + + kuponController.clear(); + } else { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kupon tidak ditemukan pada kursus ini", + style: TextStyle( + color: Colors.black, + fontSize: 15, + )), + animationType: AnimationType.fromTop, + ).show(context); + } + }, + builder: (BuildContext context, + TapDebouncerFunc? onTap) { + return Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight( + 30)), + child: DefaultButton( + text: 'Tukarkan', press: onTap), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ); + } + + Widget imageCourse() { + return Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + width: double.infinity, + height: getProportionateScreenWidth(178), + child: Stack( + children: [ + Column( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenWidth(178), + child: CachedNetworkImage( + imageUrl: widget.dataDetailCourseModel.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + color: thirdColor, + ), + gradient: LinearGradient( + stops: [0.4, 0.5, 0.6], + colors: [secondaryColor, thirdColor, secondaryColor]), + ), + errorWidget: (context, url, error) => Icon(Icons.error), + ), + ), + ], + ), + ], + ), + ); + } + + Widget buttonKuponWishlist() { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Consumer<detailCourseProv.DetailCourseProvider>( + builder: (context, state, _) { + if (state.state == detailCourseProv.ResultState.HasData) { + var detailCourse = state.result!.data[0][0]; + if (detailCourse.isFreeCourse == '1') { + return SizedBox(); + } else { + if (widget.coupon == null) { + return Expanded( + child: (tokenCek == null) + ? Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2)), + child: Transform.translate( + offset: + Offset(getProportionateScreenWidth(110), 0), + child: GestureDetector( + onTap: () { + _showDialogNotLoginKupon(); + }, + child: Padding( + padding: EdgeInsets.all(2.5), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text(""), + ], + ), + ), + ), + ), + ) + : (iDuser == null) + ? Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2)), + child: Padding( + padding: EdgeInsets.all(2.5), + child: Center( + child: CircularProgressIndicator(), + )), + ) + : ('${detailCourse.instructorId}' == '$iDuser') + ? Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2)), + child: TapDebouncer( + cooldown: + const Duration(milliseconds: 3000), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Anda tidak dapat menukarkan kupon di kursus anda sendiri", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: + AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, + TapDebouncerFunc? onTap) { + return Transform.translate( + offset: Offset( + getProportionateScreenWidth( + 110), + 0), + child: GestureDetector( + onTap: onTap, + child: Padding( + padding: EdgeInsets.all(2.5), + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + SizedBox(width: 4), + Image.asset( + "assets/images/home_coupon.png", + color: primaryColor, + ), + SizedBox(width: 10), + ], + ), + ), + ), + ); + }, + ), + ) + : (detailCourse.status_course != 'active') + ? Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth( + 2)), + child: TapDebouncer( + cooldown: const Duration( + milliseconds: 3000), + onTap: () async => await { + CherryToast.error( + animationDuration: + Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: + AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, + TapDebouncerFunc? onTap) { + return Transform.translate( + offset: Offset( + getProportionateScreenWidth( + 110), + 0), + child: GestureDetector( + onTap: onTap, + child: Padding( + padding: + EdgeInsets.all(2.5), + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + SizedBox(width: 4), + Image.asset( + "assets/images/home_coupon.png", + color: primaryColor, + ), + SizedBox(width: 10), + ], + ), + ), + ), + ); + }, + ), + ) + : (isAdmin == true) + ? Container( + margin: EdgeInsets.only( + left: + getProportionateScreenWidth( + 2)), + child: GestureDetector( + onTap: () {}, + child: Transform.translate( + offset: Offset( + getProportionateScreenWidth( + 110), + 0), + child: Padding( + padding: + EdgeInsets.all(2.5), + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + SizedBox(width: 4), + Image.asset( + "assets/images/home_coupon.png", + color: primaryColor, + ), + SizedBox(width: 1), + ], + ), + ), + ), + ), + ) + : Transform.translate( + offset: Offset( + getProportionateScreenWidth( + 110), + 0), + child: GestureDetector( + onTap: () { + if (Condition.loginEmail || + Condition.loginFirebase) { + _showKupon(); + } else { + _showDialogNotLoginKupon(); + } + }, + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Image.asset( + "assets/images/home_coupon.png", + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ], + ), + ), + ), + ), + ); + } else { + return SizedBox.shrink(); + } + } + } else { + return const SizedBox(); + } + }, + ), + Consumer<detailCourseProv.DetailCourseProvider>( + builder: (context, state, _) { + if (state.state == detailCourseProv.ResultState.HasData) { + var detailCourse = state.result!.data[0][0]; + if (detailCourse.isFreeCourse == '1') { + return SizedBox(); + } else { + return SizedBox(width: 12); + } + } else { + return SizedBox(); + } + }), + Expanded( + child: (iDuser == null) + ? GestureDetector( + onTap: () { + _showDialogNotLogin(); + }, + child: !Condition.loginEmail && !Condition.loginFirebase + ? Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SvgPicture.asset(Theme.of(context).brightness == + Brightness.dark + ? 'assets/icons/heart_dark.svg' + : 'assets/icons/heart.svg'), + ], + ), + ) + : Padding( + padding: EdgeInsets.all(2.5), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [CircularProgressIndicator()], + ), + ), + ) + : ('${widget.dataDetailCourseModel.instructorId}' == '$iDuser') + ? GestureDetector( + onTap: Provider.of<CartsProvider>(context) + .data + .contains(widget.dataDetailCourseModel.id) + ? checkUser + : wishlistExist, + child: !Condition.loginEmail && !Condition.loginFirebase + ? Row( + children: [ + Icon( + Icons.favorite_border, + color: Colors.white, + size: getProportionateScreenWidth(18), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Center( + child: Consumer< + wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + if (state.state == + wishlistProvider + .ResultState.Loading) { + return SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 1, + ), + ); + } + return SvgPicture.asset( + state.data.contains(widget + .dataDetailCourseModel.id) + ? Theme.of(context).brightness == + Brightness.dark + ? 'assets/icons/heart_dark_select.svg' + : 'assets/icons/heart_select.svg' + : Theme.of(context).brightness == + Brightness.dark + ? 'assets/icons/heart_dark.svg' + : 'assets/icons/heart.svg', + ); + }, + ), + ), + SizedBox( + width: getProportionateScreenWidth(6)), + Center( + child: Consumer< + wishlistProvider.WishlistProvider>( + builder: (contex, state, _) { + if (state.state == + wishlistProvider + .ResultState.Loading) { + return Text(''); + } + return Text( + state.data.contains(widget + .dataDetailCourseModel.id) + ? '' + : '', + overflow: TextOverflow.ellipsis, + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + letterSpacing: 0.2, + ), + ); + }, + ), + ), + ], + ), + ) + : (isAdmin == true) + ? GestureDetector( + onTap: () { + print('disabled admin'); + }, + child: !Condition.loginEmail && + !Condition.loginFirebase + ? Row( + children: [ + Icon( + Icons.favorite_border, + color: Colors.white, + size: getProportionateScreenWidth(18), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Center( + child: Consumer< + wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + if (state.state == + wishlistProvider + .ResultState.Loading) { + return SizedBox( + height: 20, + width: 20, + child: + CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 1, + ), + ); + } + return SvgPicture.asset( + state.data.contains(widget + .dataDetailCourseModel.id) + ? Theme.of(context) + .brightness == + Brightness.dark + ? 'assets/icons/heart_dark_select.svg' + : 'assets/icons/heart_select.svg' + : Theme.of(context) + .brightness == + Brightness.dark + ? 'assets/icons/heart_dark.svg' + : 'assets/icons/heart.svg', + ); + }, + ), + ), + SizedBox( + width: + getProportionateScreenWidth(6)), + Center( + child: Consumer< + wishlistProvider.WishlistProvider>( + builder: (contex, state, _) { + if (state.state == + wishlistProvider + .ResultState.Loading) { + return Text(''); + } + return Text( + state.data.contains(widget + .dataDetailCourseModel.id) + ? '' + : '', + overflow: TextOverflow.ellipsis, + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + letterSpacing: 0.2, + ), + ); + }, + ), + ), + ], + ), + ) + : (widget.dataDetailCourseModel.status_course == + 'prepublish') + ? GestureDetector( + onTap: Provider.of<CartsProvider>(context) + .data + .contains( + widget.dataDetailCourseModel.id) + ? checkUser + : wishlistExist, + child: !Condition.loginEmail && + !Condition.loginFirebase + ? Row( + children: [ + Icon( + Icons.favorite_border, + color: Colors.white, + size: + getProportionateScreenWidth(18), + ), + ], + ) + : Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Center( + child: Consumer< + wishlistProvider + .WishlistProvider>( + builder: (context, state, _) { + if (state.state == + wishlistProvider + .ResultState.Loading) { + return SizedBox( + height: 20, + width: 20, + child: + CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 1, + ), + ); + } + return SvgPicture.asset( + state.data.contains(widget + .dataDetailCourseModel + .id) + ? Theme.of(context) + .brightness == + Brightness.dark + ? 'assets/icons/heart_dark_select.svg' + : 'assets/icons/heart_select.svg' + : Theme.of(context) + .brightness == + Brightness.dark + ? 'assets/icons/heart_dark.svg' + : 'assets/icons/heart.svg', + ); + }, + ), + ), + SizedBox( + width: + getProportionateScreenWidth( + 6)), + Center( + child: Consumer< + wishlistProvider + .WishlistProvider>( + builder: (contex, state, _) { + if (state.state == + wishlistProvider + .ResultState.Loading) { + return Text(''); + } + return Text( + state.data.contains(widget + .dataDetailCourseModel + .id) + ? '' + : '', + overflow: + TextOverflow.ellipsis, + style: + primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + letterSpacing: 0.2, + ), + ); + }, + ), + ), + ], + ), + ) + : (widget.dataDetailCourseModel.status_course != + 'active') + ? TapDebouncer( + cooldown: + const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, + TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: !Condition.loginEmail && + !Condition.loginFirebase + ? Row( + children: [ + Icon( + Icons.favorite_border, + color: Colors.white, + size: + getProportionateScreenWidth( + 18), + ), + ], + ) + : Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Center( + child: Consumer< + wishlistProvider + .WishlistProvider>( + builder: + (context, state, _) { + if (state.state == + wishlistProvider + .ResultState + .Loading) { + return SizedBox( + height: 20, + width: 20, + child: + CircularProgressIndicator( + color: + secondaryColor, + strokeWidth: 1, + ), + ); + } + return SvgPicture.asset( + state.data.contains(widget + .dataDetailCourseModel + .id) + ? Theme.of(context) + .brightness == + Brightness + .dark + ? 'assets/icons/heart_dark_select.svg' + : 'assets/icons/heart_select.svg' + : Theme.of(context) + .brightness == + Brightness + .dark + ? 'assets/icons/heart_dark.svg' + : 'assets/icons/heart.svg', + ); + }, + ), + ), + SizedBox( + width: + getProportionateScreenWidth( + 6), + ), + Center( + child: Consumer< + wishlistProvider + .WishlistProvider>( + builder: + (contex, state, _) { + if (state.state == + wishlistProvider + .ResultState + .Loading) { + return Text(''); + } + return Text( + state.data.contains(widget + .dataDetailCourseModel + .id) + ? '' + : '', + overflow: TextOverflow + .ellipsis, + style: + primaryTextStyle + .copyWith( + fontSize: + getProportionateScreenWidth( + 12), + letterSpacing: 0.2, + ), + ); + }, + ), + ), + ], + ), + ); + }, + ) + : GestureDetector( + onTap: Provider.of< + wishlistProvider + .WishlistProvider>(context) + .data + .contains( + widget.dataDetailCourseModel.id) + ? checkUser + : wishlistExist, + child: !Condition.loginEmail && + !Condition.loginFirebase + ? Center( + child: Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Icon( + Icons.favorite_border, + color: Colors.white, + size: + getProportionateScreenWidth( + 18), + ), + ], + ), + ) + : Row( + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + Center( + child: Consumer< + wishlistProvider + .WishlistProvider>( + builder: (context, state, _) { + if (state.state == + wishlistProvider + .ResultState + .Loading) { + return SizedBox( + height: 20, + width: 20, + child: + CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 1, + ), + ); + } + return SvgPicture.asset( + state.data.contains(widget + .dataDetailCourseModel + .id) + ? Theme.of(context) + .brightness == + Brightness + .dark + ? 'assets/icons/heart_dark_select.svg' + : 'assets/icons/heart_select.svg' + : Theme.of(context) + .brightness == + Brightness + .dark + ? 'assets/icons/heart_dark.svg' + : 'assets/icons/heart.svg', + ); + }, + ), + ), + SizedBox( + width: + getProportionateScreenWidth( + 6)), + Center( + child: Consumer< + wishlistProvider + .WishlistProvider>( + builder: (contex, state, _) { + if (state.state == + wishlistProvider + .ResultState + .Loading) { + return Text(''); + } + return Text( + state.data.contains(widget + .dataDetailCourseModel + .id) + ? '' + : '', + overflow: + TextOverflow.ellipsis, + style: primaryTextStyle + .copyWith( + fontSize: + getProportionateScreenWidth( + 12), + letterSpacing: 0.2, + ), + ); + }, + ), + ), + ], + ), + ), + ), + ], + ); + } + + initState() { + super.initState(); + cekadmin(); + } + + return Stack( + children: [ + Container( + width: SizeConfig.screenWidth, + child: Column( + children: [ + imageCourse(), + SizedBox(height: getProportionateScreenHeight(10)), + Padding( + padding: EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(15)), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Expanded untuk menghindari overflow pada judul kursus + Expanded( + child: Text( + widget.dataDetailCourseModel.title ?? ' ', + style: thirdTextStyle.copyWith( + fontSize: widget.dataDetailCourseModel.title!.length > 72 + ? getProportionateScreenWidth(13) + : getProportionateScreenHeight(14), + fontWeight: semiBold, + ), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + + + SizedBox(width: getProportionateScreenWidth(10)), + + if (widget.coupon == null) + widget.dataDetailCourseModel.isMine != 1 + ? Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + // Harga promo atau harga diskon + Text( + widget.dataDetailCourseModel.promoPrice != '0' + ? numberFormat(widget.dataDetailCourseModel.promoPrice) + : widget.dataDetailCourseModel.discountPrice == '0' && + widget.dataDetailCourseModel.price == '0' + ? 'Gratis' + : widget.dataDetailCourseModel.discountPrice == '0' + ? numberFormat(widget.dataDetailCourseModel.price) + : numberFormat(widget.dataDetailCourseModel.discountPrice), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.23, + fontWeight: semiBold, + ), + ), + SizedBox(height: getProportionateScreenHeight(3)), + + // Harga asli yang dicoret jika ada diskon + if (widget.dataDetailCourseModel.promoPrice != '0' || + widget.dataDetailCourseModel.discountPrice != '0') + Text( + numberFormat(widget.dataDetailCourseModel.price), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + letterSpacing: 0.23, + color: secondaryColor, + decoration: TextDecoration.lineThrough, + ), + ), + ], + ) + : SizedBox.shrink(), + + if (widget.coupon != null) + widget.dataDetailCourseModel.isMine != 1 + ? Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + finalPriceCoupon.toString() == "0" + ? "Gratis" + : numberFormat(finalPriceCoupon.toString()), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.23, + fontWeight: semiBold, + ), + ), + SizedBox(height: getProportionateScreenHeight(3)), + Text( + finalPriceCoupon.toString() == "0" + ? "Gratis" + : numberFormat(widget.dataDetailCourseModel.price.toString()), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + letterSpacing: 0.23, + color: secondaryColor, + decoration: TextDecoration.lineThrough, + ), + ), + ], + ) + : SizedBox.shrink(), + ], + ), +), + + + SizedBox(height: getProportionateScreenHeight(7)), + MuridAndRating( + dataDetailCourseModel: widget.dataDetailCourseModel), + Consumer<detailCourseProv.DetailCourseProvider>( + builder: (context, state, _) { + if (state.state == detailCourseProv.ResultState.HasData) { + var detailCourse = state.result!.data[0][0]; + if (detailCourse.isMine == 1) { + return SizedBox(height: getProportionateScreenHeight(30)); + } else { + return Transform.translate( + offset: Offset(0, getProportionateScreenHeight(-30)), + child: buttonKuponWishlist(), + ); + } + } else { + return Container(); + } + }, + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/screens/detail_course/components/header_coupon.dart b/lib/screens/detail_course/components/header_coupon.dart new file mode 100644 index 0000000..78b87c9 --- /dev/null +++ b/lib/screens/detail_course/components/header_coupon.dart @@ -0,0 +1,604 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart' + as detailCourseProv; +import 'package:initial_folder/providers/radeem_voucher_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart' + as wishlistProvider; +import 'package:initial_folder/providers/whislist_provider.dart'; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/checkout/components/field_kupon.dart'; +import 'package:initial_folder/screens/detail_course/components/murid_and_rating.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class HeaderCoupon extends StatelessWidget { + const HeaderCoupon({ + Key? key, + required this.dataDetailCourseModel, + }) : super(key: key); + + final DataDetailCourseModel dataDetailCourseModel; + + @override + Widget build(BuildContext context) { + WishlistPostProvider wishlistPostProvider = + Provider.of<WishlistPostProvider>(context); + // var finalRating = + // double.parse((course.specificRating![0] / 20).toStringAsFixed(2)); + + final kuponController = TextEditingController(); + bool isLoading = false; + + Future _showDialogNotLogin() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum menambahkan ke wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + GestureDetector( + onTap: () => Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false), + child: Text('Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ], + ), + ); + } + + Future _showDialogNotLoginKupon() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum tukar kupon', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + GestureDetector( + onTap: () => Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false), + child: Text('Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ], + ), + ); + } + + addWishlist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .addWishlist(int.parse(dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + await Provider.of<CartProvider>(context, listen: false) + .addCart(dataDetailCourseModel.id); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + } + } + + addWishlistNotExist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .addWishlist(int.parse(dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + } + } + + checkUser() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + addWishlist(); + } else { + return _showDialogNotLogin(); + } + } + + wishlistExist() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + addWishlistNotExist(); + } else { + return _showDialogNotLogin(); + } + } + + Future _showKupon() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(3, 1, 6, 30), + content: SingleChildScrollView( + child: Container( + width: getProportionateScreenWidth(400), + height: getProportionateScreenHeight(400), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: Icon(Icons.cancel_rounded), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + Center( + child: Column( + children: [ + SizedBox( + height: getProportionateScreenHeight(30), + ), + Image.asset('assets/images/discount_coupon.png'), + SizedBox( + height: getProportionateScreenHeight(26), + ), + Text( + 'Tukarkan Voucher', + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + Text( + 'Masukkan kode kupon untuk klaim\npromo menarik Vocasia', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12)), + ), + SizedBox( + height: getProportionateScreenHeight(30), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: FieldKupon( + controler: kuponController, + ), + ), + SizedBox( + height: getProportionateScreenHeight(7), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: isLoading + ? LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: Color(0xff050505), + ) + : DefaultButton( + text: 'Tukarkan', + press: () async { + final voucher = kuponController.text; + if (await Provider.of< + RadeemVoucherProvider>(context, + listen: false) + .radeemVoucher( + int.parse(dataDetailCourseModel.id), + voucher)) { + await Provider.of<CartsProvider>(context, + listen: false) + .getCarts(); + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => CartPage( + idcourse: dataDetailCourseModel.id, + isiVoucher: voucher, + ), + ), + ); + + kuponController.clear(); + + // _showMessage('Voucher Berhasil Digunakan'); + } else { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar( + content: Text( + "Kursus sudah di keranjang\natau kupon tidak valid"), + duration: Duration(seconds: 4), + )); + } + }, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget imageCourse() { + return Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + width: double.infinity, + height: getProportionateScreenWidth(178), + child: Column( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenWidth(178), + child: CachedNetworkImage( + imageUrl: dataDetailCourseModel.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: Radius.circular(5), bottom: Radius.circular(5)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.fill, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + color: thirdColor, + ), + gradient: LinearGradient( + stops: [0.4, 0.5, 0.6], + colors: [secondaryColor, thirdColor, secondaryColor])), + errorWidget: (context, url, error) => Icon(Icons.error), + ), + ), + ], + ), + ); + } + + Widget buttonKuponWishlist() { + return Row( + children: [ + // Consumer<detailCourseProv.DetailCourseProvider>( + // builder: (context, state, _) { + // if (state.state == detailCourseProv.ResultState.HasData) { + // var detailCourse = state.result!.data[0][0]; + // if (detailCourse.isFreeCourse == '1') { + // return SizedBox(width: 60); + // } else { + // return Container( + // margin: + // EdgeInsets.only(left: getProportionateScreenWidth(2)), + // child: ElevatedButton( + // style: ElevatedButton.styleFrom( + // primary: Color(0xFF2D2D2D), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(6))), + // onPressed: () { + // (Condition.loginEmail || Condition.loginFirebase) + // ? _showKupon() + // : _showDialogNotLoginKupon(); + // }, + // child: Padding( + // padding: EdgeInsets.symmetric( + // horizontal: getProportionateScreenWidth(3), + // vertical: getProportionateScreenHeight(6)), + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.start, + // children: [ + // Image.asset( + // "assets/images/home_coupon.png", + // color: Colors.white, + // ), + // SizedBox( + // width: getProportionateScreenWidth(10), + // ), + // Text( + // 'Tukar Kupon', + // textAlign: TextAlign.start, + // style: thirdTextStyle.copyWith( + // color: Colors.white, + // fontSize: getProportionateScreenWidth(12), + // fontWeight: reguler), + // ), + // ], + // ), + // ), + // ), + // ); + // } + // } else { + // return Container(); + // } + // }, + // ), + + SizedBox(width: 60), + SizedBox( + width: getProportionateScreenWidth(16), + ), + // Consumer<WishlistProvider>(builder: (context, state, _) { + // return Text(state.data.contains(dataDetailCourseModel.id) + // ? 'ada di wishlist' + // : 'ga ada'); + // }), + Container( + width: getProportionateScreenWidth(173), + height: getProportionateScreenHeight(28), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(12), + vertical: getProportionateScreenHeight(6)), + // color: Color(0xFF2D2D2D), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: Color(0xFF2D2D2D)), + child: GestureDetector( + onTap: Provider.of<CartsProvider>(context) + .data + .contains(dataDetailCourseModel.id) + ? checkUser + : wishlistExist, + child: !Condition.loginEmail && !Condition.loginFirebase + ? Row( + children: [ + Icon( + Icons.favorite_border, + color: Colors.white, + size: getProportionateScreenWidth(18), + ), + SizedBox( + width: getProportionateScreenWidth(6), + ), + Text( + 'Tambah ke Wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.2), + ), + ], + ) + : Row( + children: [ + Consumer<wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + if (state.state == + wishlistProvider.ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 1, + ), + ); + } + return Icon( + state.data.contains(dataDetailCourseModel.id) + ? Icons.favorite_outlined + : Icons.favorite_border, + color: + state.data.contains(dataDetailCourseModel.id) + ? Color(0xffCD2228) + : Colors.white, + size: getProportionateScreenWidth(18), + ); + }, + ), + SizedBox( + width: getProportionateScreenWidth(6), + ), + Consumer<wishlistProvider.WishlistProvider>( + builder: (contex, state, _) { + if (state.state == + wishlistProvider.ResultState.Loading) { + return Text(''); + } + return Text( + state.data.contains(dataDetailCourseModel.id) + ? 'Sudah dalam wihslist' + : 'Tambah ke Wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.2), + ); + }, + ), + ], + ), + ), + ), + ], + ); + } + + return Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(15), + right: getProportionateScreenWidth(15)), + width: SizeConfig.screenWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(232), + child: Text(dataDetailCourseModel.title ?? ' ', + style: primaryTextStyle.copyWith( + letterSpacing: 0.1, + fontSize: getProportionateScreenHeight(14)), + maxLines: 3, + overflow: TextOverflow.ellipsis), + ), + SizedBox( + height: getProportionateScreenHeight(13), + ), + // Row( + // // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // RatingBarIndicator( + // itemSize: getProportionateScreenWidth(10), + // rating: double.parse( + // dataDetailCourseModel.rating[0].avgRating != null + // ? '${dataDetailCourseModel.rating[0].avgRating}' + // : '5.0'), + // direction: Axis.horizontal, + // itemCount: 5, + // //itemPadding: EdgeInsets.symmetric(horizontal: 4.0), + // itemBuilder: (context, _) => + // FaIcon(FontAwesomeIcons.solidStar, color: primaryColor)), + // SizedBox( + // width: getProportionateScreenWidth(4), + // ), + // Text( + // double.parse(dataDetailCourseModel.rating[0].avgRating != null + // ? '${dataDetailCourseModel.rating[0].avgRating}' + // : '5.0') + // .toString(), + // style: primaryTextStyle.copyWith( + // fontSize: getProportionateScreenWidth(10), + // color: secondaryColor, + // fontWeight: reguler), + // ), + // SizedBox( + // width: getProportionateScreenWidth(4), + // ), + // Text( + // // '(${course.numberOfRatings.toString()})', + // '(${dataDetailCourseModel.rating[0].totalReview})', + // style: primaryTextStyle.copyWith( + // fontSize: getProportionateScreenWidth(10), + // color: secondaryColor, + // fontWeight: reguler), + // ), + // ], + // ), + SizedBox( + height: getProportionateScreenHeight(9), + ), + MuridAndRating( + dataDetailCourseModel: dataDetailCourseModel, + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + imageCourse(), + SizedBox( + height: getProportionateScreenHeight(11), + ), + // Consumer<detailCourseProv.DetailCourseProvider>( + // builder: (context, state, _) { + // if (state.state == detailCourseProv.ResultState.HasData) { + // var detailCourse = state.result!.data[0][0]; + // if (detailCourse.isMine == 1) { + // return Container(); + // } else { + // return buttonKuponWishlist(); + // } + // } else { + // return Container(); + // } + // }, + // ), + buttonKuponWishlist(), + SizedBox( + height: getProportionateScreenHeight(11), + ), + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/image_course.dart b/lib/screens/detail_course/components/image_course.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/screens/detail_course/components/image_course.dart @@ -0,0 +1 @@ + diff --git a/lib/screens/detail_course/components/instructor_students_course.dart b/lib/screens/detail_course/components/instructor_students_course.dart new file mode 100644 index 0000000..275632d --- /dev/null +++ b/lib/screens/detail_course/components/instructor_students_course.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class InstructorStudentsCourses extends StatelessWidget { + const InstructorStudentsCourses( + {Key? key, required this.course, required this.murid}) + : super(key: key); + final String murid; + final String course; + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text( + ' $murid Murid', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), letterSpacing: 0.5), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Text( + ' $course Kursus', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), letterSpacing: 0.5), + ) + ], + ); + } +} diff --git a/lib/screens/detail_course/components/instruktur.dart b/lib/screens/detail_course/components/instruktur.dart new file mode 100644 index 0000000..85887d2 --- /dev/null +++ b/lib/screens/detail_course/components/instruktur.dart @@ -0,0 +1,229 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class Instruktur extends StatefulWidget { + const Instruktur({ + Key? key, + required this.id, + this.instructor, + this.bio, + this.rating, + this.review, + this.totalStudent, + this.totalLesson, + this.video = false, + this.fotoProfile, + this.headline, + }) : super(key: key); + final String id; + final String? instructor; + final String? bio; + final String? rating; + final String? review; + final String? fotoProfile; + final String? totalLesson; + final String? totalStudent; + final String? headline; + final bool video; + + @override + State<Instruktur> createState() => _InstrukturState(); +} + +class _InstrukturState extends State<Instruktur> { + bool isExpanded = false; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.symmetric(horizontal: 10, vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Instruktur Kursus', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + const SizedBox(height: 10), + Container( + decoration: BoxDecoration( + color: const Color(0xFF212121), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 16), + Container( + margin: EdgeInsets.only(left: 10), + child: CircleAvatar( + radius: 40, + backgroundColor: Colors.amber, + backgroundImage: widget.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage(widget.fotoProfile!) + as ImageProvider, + ), + ), + const SizedBox(height: 8), + Container( + margin: EdgeInsets.only(left: 10), + child: Text( + widget.instructor ?? '', + style: TextStyle(fontSize: 15), + ), + ), + SizedBox(height: 2), + widget.headline != null + ? Container( + margin: EdgeInsets.only(left: 10), + child: Text( + widget.headline!, + style: TextStyle(fontSize: 13), + ), + ) + : const SizedBox(), + SizedBox(height: 10), + Container( + margin: EdgeInsets.only(left: 10), + child: Row( + children: [ + RatingBarIndicator( + itemSize: 11, + rating: double.parse(widget.rating ?? '0'), + direction: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, _) => const FaIcon( + FontAwesomeIcons.solidStar, + color: Colors.amber, + ), + ), + SizedBox(width: 4), + Text( + double.parse(widget.rating ?? '0').toString(), + style: TextStyle(fontSize: 10), + ), + SizedBox(width: 4), + Text( + '(${widget.review ?? '0'})', + style: TextStyle(fontSize: 10), + ), + ], + ), + ), + Container( + height: 40, + margin: EdgeInsets.only(left: 10, top: 1), + child: Row( + children: [ + Text( + '${widget.totalStudent ?? '0'} Murid', + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontSize: getProportionateScreenWidth(10), + letterSpacing: 0.5), + ), + SizedBox(width: 10), + Text( + '${widget.totalLesson ?? ''} Pelajaran', + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontSize: getProportionateScreenWidth(10), + letterSpacing: 0.5), + ) + ], + ), + ), + if (widget.bio == null || widget.bio!.isEmpty) + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenHeight(10), + right: getProportionateScreenHeight(10), + bottom: getProportionateScreenHeight(10)), + child: Text( + '*Instruktur belum mencantumkan profil*', + style: TextStyle( + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + fontFamily: 'Noto Sans', + color: secondaryColor, + ), + ), + ) + else + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenHeight(5), + right: getProportionateScreenHeight(10)), + child: isExpanded + ? Html( + data: widget.bio, + style: { + "body": Style( + fontSize: FontSize(12), + fontWeight: reguler, + fontFamily: 'Noto Sans', + color: secondaryColor), + }, + ) + : Html( + data: widget.bio != null && + widget.bio!.length > 200 + ? widget.bio!.substring(0, 200) + : widget.bio!, + style: { + "body": Style( + fontSize: FontSize(12), + fontWeight: reguler, + fontFamily: 'Noto Sans', + color: secondaryColor), + }, + ), + ), + if (widget.bio!.isNotEmpty && + (widget.bio!.length > 70 || + widget.bio!.length > 200)) + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenHeight(7)), + child: TextButton( + onPressed: () { + setState(() { + isExpanded = !isExpanded; + }); + }, + child: Text( + isExpanded + ? 'Tampilkan Lebih Sedikit' + : 'Tampilkan Lebih Banyak', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + color: primaryColor, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + ), + ], + ), + ], + ) + ], + ), + ), + const SizedBox(height: 30) + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/kemampuan_diraih_list.dart b/lib/screens/detail_course/components/kemampuan_diraih_list.dart new file mode 100644 index 0000000..5b0ab50 --- /dev/null +++ b/lib/screens/detail_course/components/kemampuan_diraih_list.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class KemampuainDiraihList extends StatelessWidget { + const KemampuainDiraihList({ + Key? key, + required this.title, + }) : super(key: key); + final String title; + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 6), + child: Row( + children: [ + Icon(Icons.check, + size: getProportionateScreenHeight(13), color: thirdColor), + SizedBox( + width: 10, + ), + Expanded( + child: Align( + alignment: Alignment.topLeft, + // TODO : saat teks menjadi 2 baris menjadi tidak sejajar perlu dirapihkan + child: Text( + // TODO : MAX 75 Chareacters + title, + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.5), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/kursus_include_item.dart b/lib/screens/detail_course/components/kursus_include_item.dart new file mode 100644 index 0000000..76eace4 --- /dev/null +++ b/lib/screens/detail_course/components/kursus_include_item.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class KursusIncludeItems extends StatelessWidget { + const KursusIncludeItems({Key? key, this.svg, required this.text}) + : super(key: key); + + final String? svg; + final String text; + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(left: getProportionateScreenWidth(16)), + child: Column( + children: [ + Row( + children: [ + SvgPicture.asset( + svg!, + width: getProportionateScreenWidth(13), + height: getProportionateScreenHeight(13), + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : fourthColor, + ), + SizedBox(width: getProportionateScreenWidth(9)), + Expanded( + child: Text( + text, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: light, + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(6)), + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/murid_and_rating.dart b/lib/screens/detail_course/components/murid_and_rating.dart new file mode 100644 index 0000000..b829a6d --- /dev/null +++ b/lib/screens/detail_course/components/murid_and_rating.dart @@ -0,0 +1,262 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/posting_review_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart' + as wishlistProvider; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +class MuridAndRating extends StatelessWidget { + const MuridAndRating({Key? key, required this.dataDetailCourseModel}) + : super(key: key); + final DataDetailCourseModel dataDetailCourseModel; + @override + Widget build(BuildContext context) { + WishlistPostProvider wishlistPostProvider = + Provider.of<WishlistPostProvider>(context); + Future _showDialogNotLogin() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum menambahkan ke wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + GestureDetector( + onTap: () => Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false), + child: Text('Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ], + ), + ); + } + + // Future _showMessage() { + // return showDialog( + // context: context, + // builder: (context) => AlertDialog( + // contentPadding: EdgeInsets.fromLTRB(22, 30, 22, 30), + // content: Text( + // 'Berhasil menambahkan kursus ke wishlist', + // textAlign: TextAlign.center, + // style: primaryTextStyle.copyWith( + // fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + // ), + // ), + // ); + // } + + addWishlist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .addWishlist(int.parse(dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + await Provider.of<CartProvider>(context, listen: false) + .addCart(dataDetailCourseModel.id); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + } + } + + addWishlistNotExist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .addWishlist(int.parse(dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + } + } + + // deleteWishlist() async { + // var connectivityResult = await (Connectivity().checkConnectivity()); + // if (connectivityResult == ConnectivityResult.none) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // duration: Duration(seconds: 1), + // backgroundColor: Colors.red[600], + // content: Text( + // 'No Internet Connections', + // textAlign: TextAlign.center, + // style: primaryTextStyle.copyWith(color: Colors.white), + // ), + // behavior: SnackBarBehavior.floating, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(5), + // ), + // ), + // ); + // } else { + // await wishlistPostProvider + // .addWishlist(int.parse(dataDetailCourseModel.id)); + // } + // } + + checkUser() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + addWishlist(); + } else { + return _showDialogNotLogin(); + } + } + + wishlistExist() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + addWishlistNotExist(); + } else { + return _showDialogNotLogin(); + } + } + + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(15)), + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + "assets/icons/student.svg", + width: getProportionateScreenWidth(17), + height: getProportionateScreenHeight(17), + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : fourthColor, + ), + SizedBox(width: getProportionateScreenWidth(4)), + Text( + ' ${dataDetailCourseModel.totalStudents != null ? dataDetailCourseModel.totalStudents : "0"} Murid', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: medium, + ), + ) + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + dataDetailCourseModel.rating[0].avgRating != null && + dataDetailCourseModel.rating[0].avgRating != 0 + ? Text( + double.parse('${dataDetailCourseModel.rating[0].avgRating}') + .toString(), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + fontWeight: medium, + ), + ) + : SizedBox.shrink(), + + SizedBox(width: getProportionateScreenWidth(4)), + RatingBarIndicator( + itemPadding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(2)), + itemSize: getProportionateScreenWidth(13), + rating: dataDetailCourseModel.rating[0].avgRating != null && + dataDetailCourseModel.rating[0].avgRating != 0 + ? double.parse('${dataDetailCourseModel.rating[0].avgRating}') + : 5.0, + direction: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, _) => FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + ), + ), + if (dataDetailCourseModel.rating[0].totalReview != null && + int.tryParse('${dataDetailCourseModel.rating[0].totalReview}') != null && + int.parse('${dataDetailCourseModel.rating[0].totalReview}') > 0) + ...[ + SizedBox(width: getProportionateScreenWidth(4)), + Text( + '(${dataDetailCourseModel.rating[0].totalReview} Reviews)', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: medium, + ), + ), + ] + else + SizedBox.shrink(), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/detail_course/components/murid_and_whislist.dart b/lib/screens/detail_course/components/murid_and_whislist.dart new file mode 100644 index 0000000..e57247b --- /dev/null +++ b/lib/screens/detail_course/components/murid_and_whislist.dart @@ -0,0 +1,283 @@ +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/cart_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/posting_review_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart' + as wishlistProvider; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class MuridAndWhislist extends StatelessWidget { + const MuridAndWhislist({Key? key, required this.dataDetailCourseModel}) + : super(key: key); + final DataDetailCourseModel dataDetailCourseModel; + @override + Widget build(BuildContext context) { + WishlistPostProvider wishlistPostProvider = + Provider.of<WishlistPostProvider>(context); + Future _showDialogNotLogin() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum menambahkan ke wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + GestureDetector( + onTap: () => Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false), + child: Text('Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ], + ), + ); + } + + // Future _showMessage() { + // return showDialog( + // context: context, + // builder: (context) => AlertDialog( + // contentPadding: EdgeInsets.fromLTRB(22, 30, 22, 30), + // content: Text( + // 'Berhasil menambahkan kursus ke wishlist', + // textAlign: TextAlign.center, + // style: primaryTextStyle.copyWith( + // fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + // ), + // ), + // ); + // } + + addWishlist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .addWishlist(int.parse(dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + await Provider.of<CartProvider>(context, listen: false) + .addCart(dataDetailCourseModel.id); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + } + } + + addWishlistNotExist() async { + var connectivityResult = await (Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 1), + backgroundColor: Colors.red[600], + content: Text( + 'No Internet Connections', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith(color: Colors.white), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + await wishlistPostProvider + .addWishlist(int.parse(dataDetailCourseModel.id)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + } + } + + // deleteWishlist() async { + // var connectivityResult = await (Connectivity().checkConnectivity()); + // if (connectivityResult == ConnectivityResult.none) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // duration: Duration(seconds: 1), + // backgroundColor: Colors.red[600], + // content: Text( + // 'No Internet Connections', + // textAlign: TextAlign.center, + // style: primaryTextStyle.copyWith(color: Colors.white), + // ), + // behavior: SnackBarBehavior.floating, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(5), + // ), + // ), + // ); + // } else { + // await wishlistPostProvider + // .addWishlist(int.parse(dataDetailCourseModel.id)); + // } + // } + + checkUser() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + addWishlist(); + } else { + return _showDialogNotLogin(); + } + } + + wishlistExist() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + addWishlistNotExist(); + } else { + return _showDialogNotLogin(); + } + } + + return Container( + child: Row( + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.people_outline, + color: Colors.white, + size: getProportionateScreenWidth(22), + ), + SizedBox(width: getProportionateScreenWidth(4)), + Text( + ' ${dataDetailCourseModel.totalStudents} Murid', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + letterSpacing: 0.5), + ) + ], + ), + SizedBox( + width: getProportionateScreenWidth(15), + ), + // Consumer<WishlistProvider>(builder: (context, state, _) { + // return Text(state.data.contains(dataDetailCourseModel.id) + // ? 'ada di wishlist' + // : 'ga ada'); + // }), + GestureDetector( + onTap: Provider.of<CartsProvider>(context) + .data + .contains(dataDetailCourseModel.id) + ? checkUser + : wishlistExist, + child: !Condition.loginEmail + ? Row( + children: [ + Icon( + Icons.favorite_border, + color: Colors.white, + size: getProportionateScreenWidth(22), + ), + SizedBox( + width: getProportionateScreenWidth(4), + ), + Text( + 'Tambah ke wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + letterSpacing: 0.2), + ), + ], + ) + : Row( + children: [ + Consumer<wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + if (state.state == + wishlistProvider.ResultState.Loading) { + return Container( + height: 15, + width: 15, + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 1, + ), + ); + } + return Icon( + state.data.contains(dataDetailCourseModel.id) + ? Icons.favorite_outlined + : Icons.favorite_border, + color: state.data.contains(dataDetailCourseModel.id) + ? Color(0xffCD2228) + : Colors.white, + size: getProportionateScreenWidth(22), + ); + }, + ), + SizedBox( + width: getProportionateScreenWidth(4), + ), + Consumer<wishlistProvider.WishlistProvider>( + builder: (contex, state, _) { + if (state.state == + wishlistProvider.ResultState.Loading) { + return Text(''); + } + return Text( + state.data.contains(dataDetailCourseModel.id) + ? 'Sudah dalam wihslist' + : 'Tambah ke wishlist', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + letterSpacing: 0.2), + ); + }, + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/detail_course/components/tab_bar_items.dart b/lib/screens/detail_course/components/tab_bar_items.dart new file mode 100644 index 0000000..7fccb20 --- /dev/null +++ b/lib/screens/detail_course/components/tab_bar_items.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/tab_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class TabBarItems extends StatelessWidget { + final int index; + final String title; + + const TabBarItems({ + Key? key, + required this.index, + required this.title, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + TabProvider tab = Provider.of<TabProvider>(context, listen: false); + + return GestureDetector( + onTap: () { + tab.currentIndex = index; + }, + child: Container( + child: Column( + children: [ + Text( + title, + style: thirdTextStyle.copyWith( + color: tab.currentIndex == index + ? Theme.of(context).brightness == Brightness.light + ? primaryColorligtmode + : primaryColor + : fifteenColor, + fontSize: getProportionateScreenWidth(12), + fontWeight: semiBold, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + Container( + width: getProportionateScreenWidth(58), + height: getProportionateScreenHeight(2), + decoration: BoxDecoration( + color: tab.currentIndex == index + ? Theme.of(context).brightness == Brightness.light + ? primaryColorligtmode + : primaryColor + : Colors.transparent, + borderRadius: + BorderRadius.circular(getProportionateScreenHeight(19)), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/detail_course/components/terkait.dart b/lib/screens/detail_course/components/terkait.dart new file mode 100644 index 0000000..ceb207a --- /dev/null +++ b/lib/screens/detail_course/components/terkait.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/screens/home/components/body_comp/course_terkait.dart'; +import 'package:initial_folder/size_config.dart'; + +class Terkait extends StatelessWidget { + const Terkait({ + Key? key, + this.idCategory, + }) : super(key: key); + + final String? idCategory; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(20), + left: getProportionateScreenWidth(10), + ), + child: Container( + height: getProportionateScreenHeight(240), + child: Column( + children: [ + Expanded( + child: CourseTerkait( + name: "", + categoryId: idCategory ?? "", + subId: "", + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/detail_course/components/ulasan.dart b/lib/screens/detail_course/components/ulasan.dart new file mode 100644 index 0000000..a6b1b20 --- /dev/null +++ b/lib/screens/detail_course/components/ulasan.dart @@ -0,0 +1,430 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/models/detail_rating_course_model.dart'; +import 'package:initial_folder/providers/detail_rating_course_provider.dart'; +import 'package:initial_folder/screens/detail_course/components/detail_list_ulasan.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class Ulasan extends StatefulWidget { + const Ulasan({ + Key? key, + required this.id, + }) : super(key: key); + final String id; + + @override + _UlasanState createState() => _UlasanState(); +} + +class _UlasanState extends State<Ulasan> { + int _currentPage = 1; + int _totalPages = 1; + List<DataReview> _filteredReviews = []; // Menyimpan review yang sudah difilter + + // Fungsi untuk membatasi ulasan per halaman + List<DataReview> _getPaginatedReviews(List<DataReview> reviews) { + int startIndex = (_currentPage - 1) * 5; + int endIndex = startIndex + 5; + return reviews.sublist(startIndex, endIndex.clamp(0, reviews.length)); + } + + void _filterReviews(int rating, List<DataReview> allReviews) { + setState(() { + _filteredReviews = allReviews.where((review) => double.parse(review.rating ?? '0') == rating).toList(); + _totalPages = (_filteredReviews.length / 5).ceil(); + _currentPage = 1; // Reset ke halaman pertama saat filter diubah + }); + } + + @override + Widget build(BuildContext context) { + final listChoices = <ItemChoice>[ + ItemChoice(0, 0, 'Semua '), + ItemChoice(5, 1, '5'), + ItemChoice(4, 1, '4'), + ItemChoice(3, 1, '3'), + ItemChoice(2, 1, '2'), + ItemChoice(1, 1, '1'), + ]; + + return SingleChildScrollView( + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(10), + ), + child: Consumer<DetailRatingCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ), + ); + } else if (state.state == ResultState.HasData) { + var ulasan = state.result; + + // Hitung total halaman berdasarkan jumlah ulasan jika semua ulasan ditampilkan + if (state.currentIndex == 0) { + _filteredReviews = ulasan!.dataReview; + _totalPages = (_filteredReviews.length / 5).ceil(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(20)), + Text( + 'Ulasan Kursus', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(15), + ), + ), + Row( + children: [ + Text( + ulasan!.data.avgRating is List<dynamic> + ? '0' + : double.parse(ulasan.data.avgRating) + .toStringAsFixed(1) + .replaceAll('.', ','), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(29), + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(2)), + RatingBarIndicator( + itemSize: getProportionateScreenWidth(10), + rating: ulasan.data.avgRating is List<dynamic> + ? 5.0 + : double.parse(ulasan.data.avgRating), + direction: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, _) => FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + ), + ), + SizedBox( + height: getProportionateScreenHeight(2)), + Text( + '(${ulasan.dataReview.length} Ulasan)', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(11)), + VerticalRatingBar( + lebar: ulasan.data.precentageRating.rating5.toString(), + text: '5.0', + total: (ulasan.data.precentageRating.rating5.runtimeType == String) + ? '${persentaseUlasan(ulasan.data.precentageRating.rating5)}' + : '${ulasan.data.precentageRating.rating5}'), + VerticalRatingBar( + lebar: ulasan.data.precentageRating.rating4.toString(), + text: '4.0', + total: (ulasan.data.precentageRating.rating4.runtimeType == String) + ? '${persentaseUlasan(ulasan.data.precentageRating.rating4)}' + : '${ulasan.data.precentageRating.rating4}'), + VerticalRatingBar( + lebar: ulasan.data.precentageRating.rating3.toString(), + text: '3.0', + total: (ulasan.data.precentageRating.rating3.runtimeType == String) + ? '${persentaseUlasan(ulasan.data.precentageRating.rating3)}' + : '${ulasan.data.precentageRating.rating3}'), + VerticalRatingBar( + lebar: ulasan.data.precentageRating.rating2.toString(), + text: '2.0', + total: (ulasan.data.precentageRating.rating2.runtimeType == String) + ? '${persentaseUlasan(ulasan.data.precentageRating.rating2)}' + : '${ulasan.data.precentageRating.rating2}'), + VerticalRatingBar( + lebar: ulasan.data.precentageRating.rating1.toString(), + text: '1.0', + total: (ulasan.data.precentageRating.rating1.runtimeType == String) + ? '${persentaseUlasan(ulasan.data.precentageRating.rating1)}' + : '${ulasan.data.precentageRating.rating1}'), + ], + ); + } + return Text( + 'Terjadi Kesalan ', + style: primaryTextStyle, + ); + }), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(11)), + Consumer<DetailRatingCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.HasData) { + var ulasan = state.result; + return Column( + children: [ + SizedBox(height: getProportionateScreenHeight(8)), + Wrap( + spacing: 6, + runSpacing: 3, + children: listChoices + .map( + (e) => InkWell( + onTap: () { + state.currentIndex = e.id; + if (state.currentIndex == 0) { + // Reset ke semua review jika memilih filter "Semua" + Provider.of<DetailRatingCourseProvider>(context, listen: false) + .getDetailCourse(); + _filteredReviews = ulasan!.dataReview; + _totalPages = (_filteredReviews.length / 5).ceil(); + _currentPage = 1; + } else { + // Filter sesuai dengan rating yang dipilih + _filterReviews(e.id, ulasan!.dataReview); + } + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 5), + decoration: BoxDecoration( + color: state.currentIndex == e.id + ? Theme.of(context) + .colorScheme + .onBackground + : Theme.of(context) + .colorScheme + .primaryContainer, + border: Border.all( + color: Theme.of(context) + .colorScheme + .brightness == + Brightness.dark + ? seventeenColor + : secondaryColor, + ), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: baruTexthitam.withOpacity(0.2), + blurRadius: 2, + spreadRadius: 1, + offset: Offset(0, 2), + ) + ], + ), + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(5), + vertical: getProportionateScreenHeight(5), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + (e.icon == 0) + ? Text('') + : FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + size: getProportionateScreenWidth(11), + ), + SizedBox(width: getProportionateScreenWidth(3)), + Text( + e.label, + style: thirdTextStyle.copyWith( + color: state.currentIndex == e.id + ? Theme.of(context) + .colorScheme + .background + : e.label == "Semua " + ? primaryColor + : Theme.of(context) + .colorScheme + .onBackground, + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + ), + ), + ), + ) + .toList(), + ), + SizedBox(height: getProportionateScreenHeight(17)), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(15), + right: getProportionateScreenWidth(10), + ), + child: (_filteredReviews.isEmpty) + ? SizedBox(height: getProportionateScreenHeight(15)) + : Column( + children: _getPaginatedReviews(_filteredReviews) + .map( + (e) => DetailListUlasan( + review: e.review ?? '', + date: e.date ?? '-', + name: e.name ?? '', + starRating: double.parse(e.rating ?? '5'), + ), + ) + .toList()), + ), + // Navigasi Pagination + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_filteredReviews.isEmpty) + Text( + 'Belum ada rating untuk kategori bintang ini', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + ), + ) + else ...[ + if (_currentPage > 1) + IconButton( + onPressed: () { + setState(() { + _currentPage--; + }); + }, + icon: Icon(Icons.arrow_back), + ), + Text('$_currentPage of $_totalPages'), + if (_currentPage < _totalPages) + IconButton( + onPressed: () { + setState(() { + _currentPage++; + }); + }, + icon: Icon(Icons.arrow_forward), + ), + ] + ], + ), + ], + ); + } + return Text(''); + }, + ), + SizedBox( + height: 30, + ) + ], + ), + ); + } +} + +class VerticalRatingBar extends StatelessWidget { + const VerticalRatingBar({ + Key? key, + required this.text, + this.lebar = '0', + required this.total, + }) : super(key: key); + final String text; + final String total; + final String lebar; + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)), + child: Column( + children: [ + Row( + children: [ + FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + size: getProportionateScreenWidth(11), + ), + SizedBox(width: getProportionateScreenWidth(4)), + Text( + text, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(width: getProportionateScreenWidth(6)), + Expanded( + child: Column( + children: [ + Stack( + children: [ + Container( + height: getProportionateScreenWidth(4), + decoration: BoxDecoration( + color: fourthColor, + borderRadius: BorderRadius.circular(10)), + ), + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(110)) * + double.parse(persentaseUlasan(lebar) + .replaceAll(',', '.')) / + 100, + height: getProportionateScreenWidth(4), + decoration: BoxDecoration( + color: thirteenColor, + borderRadius: BorderRadius.circular(10)), + ) + ], + ), + ], + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Container( + width: getProportionateScreenWidth(30), + child: Text( + total, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + ), + textAlign: TextAlign.start, + ), + ) + ], + ), + ], + ), + ); + } +} + +class ItemChoice { + final int id; + final int icon; + final String label; + + ItemChoice(this.id, this.icon, this.label); +} diff --git a/lib/screens/detail_course/detail_course_coupon_screen.dart b/lib/screens/detail_course/detail_course_coupon_screen.dart new file mode 100644 index 0000000..4520287 --- /dev/null +++ b/lib/screens/detail_course/detail_course_coupon_screen.dart @@ -0,0 +1,770 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/helper/validator.dart'; + +import 'package:initial_folder/providers/cart_provider.dart' as cartProvider; +import 'package:initial_folder/providers/carts_provider.dart' as cartsProvider; +import 'package:initial_folder/providers/detail_course_coupon_provider.dart'; + +import 'package:initial_folder/providers/detail_rating_course_provider.dart' + as detailRatingCourseProvider; +import 'package:initial_folder/providers/instructor_provider.dart' + as instructorProvider; +import 'package:initial_folder/providers/lesson_course_provider.dart' + as lessonCourseProvider; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; +import 'package:initial_folder/providers/order_provider.dart' as orderProvider; +import 'package:initial_folder/providers/payments_provider.dart' + as paymentsProvider; +import 'package:initial_folder/providers/section_lesson_course_provider.dart' + as sectionLessonCourseProvider; +import 'package:initial_folder/providers/tab_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart' + as wishlistProvider; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/checkout/checkout_coupon_page.dart'; +import 'package:initial_folder/screens/checkout/checkout_page.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/screens/detail_course/components/header_coupon.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/my_course/success_free_course.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar.dart'; +import 'package:initial_folder/screens/detail_course/components/custom_tab_bar.dart'; +import 'package:initial_folder/screens/detail_course/components/header.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/instructor_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/services/section_lesson_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class DetailCourseCouponScreen extends StatelessWidget { + const DetailCourseCouponScreen( + {Key? key, required this.idcourse, required this.coupon}) + : super(key: key); + final String idcourse; + final String coupon; + + static String routeName = "/course_detail"; + @override + Widget build(BuildContext context) { + final typeCoupon = Provider.of<TotalPriceProvider>(context).typeCoupon; + final selected = Provider.of<TotalPriceProvider>(context); + // Provider.of<orderProvider.OrderProvider>(context).clearOrder(); + // Provider.of<orderProvider.OrderProvider>(context).clearInvoice(); + paymentsProvider.PaymentsProvider pay = + Provider.of<paymentsProvider.PaymentsProvider>(context); + SizeConfig().init(context); + showNotifDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, child) { + if (state.state == paymentsProvider.ResultState.gagal) { + return Container( + height: getProportionateScreenHeight(40), + width: getProportionateScreenWidth(15), + child: Center( + child: Text( + 'Anda sudah memiliki kursus ini', + style: primaryTextStyle.copyWith(fontSize: 12), + textAlign: TextAlign.center, + ), + ), + ); + } else { + return Text( + 'Erorr lain', + style: thirdTextStyle, + ); + } + }, + ), + ); + }, + // barrierDismissible: false, + ); + } + + Future _showDialogNotLogin(String teks) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum $teks', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + }, + child: Text('Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ], + ), + ); + } + + Future<void> _showMessage() { + return showModalBottomSheet<void>( + context: context, + builder: (BuildContext context) { + return Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + if (state.state == cartProvider.ResultState.loading) { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Center( + child: CircularProgressIndicator( + strokeWidth: 1, + color: primaryColor, + ), + )); + } else if (state.state == cartProvider.ResultState.succes) { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Column( + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.of(context).pop(); + }, + icon: Icon( + Icons.close, + color: secondaryColor, + size: 15, + )), + ], + ), + Icon( + Icons.check_rounded, + size: 40, + color: eightColor, + ), + SizedBox( + height: getProportionateScreenWidth(15), + ), + Text( + 'Berhasil menambahkan kursus ke keranjang', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 4, + letterSpacing: 0.5), + ), + SizedBox( + height: getProportionateScreenWidth(4), + ), + TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric(vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => CartPage( + idcourse: idcourse, + ))); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + letterSpacing: 0.5, + color: primaryColor), + ), + ), + ], + ), + ); + } else if (state.state == cartProvider.ResultState.failed) { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Column( + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.of(context).pop(); + }, + icon: Icon( + Icons.close, + color: secondaryColor, + size: 15, + )), + ], + ), + Icon( + Icons.check_rounded, + size: 40, + color: eightColor, + ), + SizedBox( + height: getProportionateScreenWidth(15), + ), + Text( + 'Berhasil menghapus kursus dari keranjang', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 4, + letterSpacing: 0.5), + ), + SizedBox( + height: getProportionateScreenWidth(4), + ), + TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric(vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => CartPage( + idcourse: idcourse, + ))); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + letterSpacing: 0.5, + color: primaryColor), + ), + ), + ], + ), + ); + } else { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Center( + child: Text('Terjadi Kesalahan'), + )); + } + }); + }, + ); + } + + handleNotLogin() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + _showMessage(); + await Provider.of<cartProvider.CartProvider>(context, listen: false) + .addCart(int.parse(idcourse)); + await Provider.of<cartsProvider.CartsProvider>(context, listen: false) + .getCarts(); + await Provider.of<WishlistPostProvider>(context, listen: false) + .addWishlist(int.parse(idcourse)); + await Provider.of<wishlistProvider.WishlistProvider>(context, + listen: false) + .getWishlist(); + // cartsDatabaseProvider.setCarts(id); + } else { + String teks = 'menambahkan ke keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginWishlistNotExist() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + _showMessage(); + await Provider.of<cartProvider.CartProvider>(context, listen: false) + .addCart(int.parse(idcourse)); + await Provider.of<cartsProvider.CartsProvider>(context, listen: false) + .getCarts(); + } else { + String teks = 'menambahkan ke keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginBuy( + {required String title, + required String price, + required String discountPrice, + required String instructor, + required String imageUrl}) async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .clear(); + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .addOrder( + id: idcourse, + title: title, + price: price, + discountPrice: discountPrice, + imageUrl: imageUrl, + instructor: instructor, + ); + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .getTotalPrice(discountPrice); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => CheckoutCouponPage( + discountPrice: discountPrice, + idCourse: idcourse, + instructor: instructor, + title: title, + price: price, + coupon: coupon, + ))); + } else { + String teks = 'membeli kursus'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginFree( + String? id, + String? title, + String? thumb, + String? instr, + ) async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + if (await pay.freeCourse(int.parse(idcourse))) { + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SuccessFreeCourse( + id: id, + thumbnail: thumb, + title: title, + instructor: instr, + ))); + } else { + showNotifDialog(context); + } + } else { + String teks = 'memiliki kursus ini'; + return _showDialogNotLogin(teks); + } + } + + Widget freeBottomNav(String? courseId, String? instructor, + String? thumbnail, String? title) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + border: + Border.symmetric(horizontal: BorderSide(color: fourthColor)), + boxShadow: [ + BoxShadow( + offset: Offset(0, -2), + blurRadius: 50, + color: fourthColor.withOpacity(0.15)) + ]), + child: Row( + children: [ + SizedBox( + width: 13, + ), + Text( + 'Gratis', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenHeight(20), + letterSpacing: 0.23), + ), + Spacer(), + GestureDetector( + onTap: () => + handleNotLoginFree(courseId, title, thumbnail, instructor), + child: Container( + margin: EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(123), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Text( + 'Miliki sekarang', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: Color(0xff181818), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Text( + 'Miliki sekarang', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: Color(0xff181818), + ), + ); + }), + ), + ), + ), + ], + ), + ); + } + + Widget playCourseNav(String? courseId, String? instructor, + String? thumbnail, String? title) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + border: + Border.symmetric(horizontal: BorderSide(color: fourthColor)), + boxShadow: [ + BoxShadow( + offset: Offset(0, -2), + blurRadius: 50, + color: fourthColor.withOpacity(0.15)) + ]), + child: Center( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: Size(getProportionateScreenWidth(90), + getProportionateScreenHeight(33)), + backgroundColor: primaryColor, + ), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => + lessonCourseProvider.LessonCourseProvider( + lessonCourseService: LessonCourseService(), + id: int.parse(courseId ?? '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseCouponProvider( + courseService: CourseService(), + id: courseId ?? '1', + coupon: coupon)) + ], + child: PlayCourse( + judul: title ?? '', + instruktur: instructor ?? '', + thumbnail: thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + courseeid: idcourse, + ), + ), + ), + ); + }, + child: Text( + 'Lanjutkan Belajar', + style: thirdTextStyle.copyWith(color: Colors.black), + ), + ), + )); + } + + Widget bottomNav( + {required String discountPrice, + required String idCourse, + required String title, + required String price, + required String instructor, + required String imageUrl}) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + border: + Border.symmetric(horizontal: BorderSide(color: fourthColor)), + boxShadow: [ + BoxShadow( + offset: Offset(0, -2), + blurRadius: 50, + color: fourthColor.withOpacity(0.15)) + ]), + child: Row( + children: [ + SizedBox( + width: 13, + ), + Text( + numberFormat(discountPrice.toString().replaceAll('.', '')), + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenHeight(20), + letterSpacing: 0.23), + ), + Spacer(), + Container( + width: getProportionateScreenWidth(39), + height: getProportionateScreenHeight(34), + decoration: BoxDecoration( + border: Border.all(color: primaryColor), + borderRadius: BorderRadius.circular(5), + ), + child: Consumer<wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + return IconButton( + padding: EdgeInsets.zero, + splashRadius: 17, + onPressed: () => state.data.contains(idcourse) + ? handleNotLogin() + : handleNotLoginWishlistNotExist(), + icon: Icon(FeatherIcons.shoppingCart, + color: primaryColor, size: 20), + ); + }), + ), + // ), + SizedBox( + width: getProportionateScreenWidth(12), + ), + GestureDetector( + onTap: () { + int total = int.parse(discountPrice); + selected.selectedTotalPrice = + total <= 50000 ? total + 5000 : total; + print("Ini total price buat ke va ${selected.totalPrice}"); + handleNotLoginBuy( + title: title, + discountPrice: discountPrice, + imageUrl: imageUrl, + price: price, + instructor: instructor); + }, + child: Container( + margin: EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(113), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Text( + 'Beli sekarang', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: Color(0xff181818), + ), + )), + ), + ) + ], + ), + ); + } + + return SafeArea( + child: MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => DetailCourseCouponProvider( + courseService: CourseService(), id: idcourse, coupon: coupon), + ), + ChangeNotifierProvider( + create: (context) => + detailRatingCourseProvider.DetailRatingCourseProvider( + courseService: CourseService(), id: idcourse), + ), + ChangeNotifierProvider( + create: (context) => + sectionLessonCourseProvider.SectionLessonCourseProvider( + id: idcourse, sectionLessonService: SectionLessonService()), + ), + ], + child: Consumer<DetailCourseCouponProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + var detailCourse = state.result!; + + return Scaffold( + body: ListView( + physics: ScrollPhysics(), + shrinkWrap: true, + children: [ + AppBarHeader( + idcourse: idcourse, + ), + HeaderCoupon( + dataDetailCourseModel: detailCourse, + ), + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => + instructorProvider.InstructorProvider( + instructorService: InstructorService(), + id: int.parse(detailCourse.instructorId!)), + ), + ChangeNotifierProvider( + create: (context) => TabProvider()), + ], + child: CustomTabBar( + dataDetailCourseModel: detailCourse, + totalDuration: detailCourse.totalDuration, + bio: detailCourse.bio, + instructor: detailCourse.instructor, + rating: detailCourse.rating[0].avgRating.toString(), + review: detailCourse.rating[0].totalReview, + totalLesson: detailCourse.totalLesson, + totalStudent: detailCourse.totalStudents, + ), + ), + ] + // var finalRating = double.parse( + + ), + bottomNavigationBar: (detailCourse.isMine == 1) + ? playCourseNav( + detailCourse.id, + detailCourse.instructor, + detailCourse.thumbnail, + detailCourse.title, + ) + : (detailCourse.isFreeCourse == '1' || typeCoupon == '1') + ? freeBottomNav( + detailCourse.id, + detailCourse.instructor, + detailCourse.thumbnail, + detailCourse.title, + ) + : bottomNav( + discountPrice: detailCourse.discountPrice ?? ' ', + idCourse: detailCourse.id, + instructor: detailCourse.instructor ?? '', + price: detailCourse.price ?? '', + title: detailCourse.title ?? '', + imageUrl: detailCourse.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg'), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Server internal Error'), + ], + ), + ); + } else { + return Center(child: Text('')); + } + }, + ), + ), + ); + } +} diff --git a/lib/screens/detail_course/detail_course_screen.dart b/lib/screens/detail_course/detail_course_screen.dart new file mode 100644 index 0000000..e85d2cd --- /dev/null +++ b/lib/screens/detail_course/detail_course_screen.dart @@ -0,0 +1,2000 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/tab_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/checkout/checkout_cart_page.dart'; +import 'package:initial_folder/screens/checkout/detail_zero_payment.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/my_course/success_free_course.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar.dart'; +import 'package:initial_folder/screens/detail_course/components/custom_tab_bar.dart'; +import 'package:initial_folder/screens/detail_course/components/header.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/instructor_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/services/section_lesson_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; +import '../../models/history_transaction_model.dart'; +import '../../providers/history_transactions_provider.dart' hide ResultState; +import '../../services/user_info_service.dart'; +import 'package:initial_folder/providers/cart_provider.dart' as cartProvider; +import 'package:initial_folder/providers/carts_provider.dart' as cartsProvider; +import 'package:initial_folder/providers/detail_rating_course_provider.dart' + as detailRatingCourseProvider; +import 'package:initial_folder/providers/instructor_provider.dart' + as instructorProvider; +import 'package:initial_folder/providers/lesson_course_provider.dart' + as lessonCourseProvider; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; +import 'package:initial_folder/providers/order_provider.dart' as orderProvider; +import 'package:initial_folder/providers/payments_provider.dart' + as paymentsProvider; +import 'package:initial_folder/providers/section_lesson_course_provider.dart' + as sectionLessonCourseProvider; +import 'package:initial_folder/providers/whislist_provider.dart' + as wishlistProvider; + +class DetailCourseScreen extends StatefulWidget { + const DetailCourseScreen({ + Key? key, + this.resoaktifitas, + this.isPromo, + required this.idcourse, + }) : super(key: key); + + final String idcourse; + final String? resoaktifitas; + final bool? isPromo; + static String routeName = "/course_detail"; + + @override + _DetailCourseScreenState createState() => _DetailCourseScreenState(); +} + +class _DetailCourseScreenState extends State<DetailCourseScreen> { + late DataDetailCourseModel dataDetailCourseModel; + late String cekstrAdmin = ''; + late int? iDuser; + bool _isAdminChecked = false; + bool isowned = false; + bool isCoursePending = false; + ScrollController _scrollController = ScrollController(); + Color appBarColor = Colors.transparent; + + Future<bool> _isCourseInPendingPayment(String courseId) async { + List<HistoryTransactionModel>? pendingTransactions = + Provider.of<HistoryTranscationsProvider>(context, listen: false).paymentPending; + + List<HistoryTransactionModel>? paymentAwaitingMethod = + Provider.of<HistoryTranscationsProvider>(context, listen: false).paymentAwaitingMethod; + + // Cek jika ada transaksi yang sedang pending + if (pendingTransactions != null) { + for (var transaction in pendingTransactions) { + if (transaction.courses?.any((course) => course.courseId == courseId) ?? false) { + return true; + } + } + } + + if (paymentAwaitingMethod != null) { + for (var transaction in paymentAwaitingMethod) { + if (transaction.courses?.any((course) => course.courseId == courseId) ?? false) { + return true; + } + } + } + + return false; +} + + + @override + void initState() { + super.initState(); + _scrollController.addListener(() { + if (_scrollController.position.pixels > 50) { + setState(() { + appBarColor = Theme.of(context).colorScheme.background; + }); + } else { + setState(() { + appBarColor = Colors.transparent; + }); + } + }); + cekadminbaru(); + getIDuser(); + checkPendingStatus(); + + } + + void checkPendingStatus() async { + bool pendingStatus = await _isCourseInPendingPayment(widget.idcourse); + setState(() { + isCoursePending = pendingStatus; + }); + } + + void fetchCourseDetails() async { + print("Data kursus di-refresh"); + setState(() { + isowned = true; + }); + await Provider.of<myCourseProvider.MyCourseProvider>(context, listen: false) + .getMyCourse(); + } + + void navigateAndRefresh(String thumbn, String title, String instruct) async { + var result = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SuccessFreeCourse( + id: widget.idcourse, + thumbnail: thumbn, + title: title, + instructor: instruct, + ), + ), + ); + + if (result == true) { + setState(() { + Provider.of<myCourseProvider.MyCourseProvider>(context, listen: false) + .getMyCourse(); + fetchCourseDetails(); + }); + } + } + + void getIDuser() { + UsersInfo().getIdUser().then((value) => { + setState(() { + iDuser = value; + }), + }); + } + + void cekadminbaru() { + if (!_isAdminChecked) { + UserInfoService().getDataDiriADMIN().then((value) => { + if (value == 401) + { + setState(() { + cekstrAdmin = 'admin'; + }), + } + else + { + setState(() { + cekstrAdmin = 'user'; + }), + }, + _isAdminChecked = true, + }); + } + } + + @override + Widget build(BuildContext context) { + List<String> idCarts = []; + final selectedTotalPrice = Provider.of<TotalPriceProvider>(context); + paymentsProvider.PaymentsProvider pay = + Provider.of<paymentsProvider.PaymentsProvider>(context); + SizeConfig().init(context); + final themeProvider = Provider.of<ThemeProvider>(context); + + showNotifDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, child) { + if (state.state == paymentsProvider.ResultState.gagal) { + return Container( + height: getProportionateScreenHeight(40), + width: getProportionateScreenWidth(15), + child: Center( + child: Text( + 'Anda sudah memiliki kursus ini', + style: primaryTextStyle.copyWith(fontSize: 12), + textAlign: TextAlign.center, + ), + ), + ); + } else { + Future.delayed(Duration.zero, () { + Navigator.pop(context); + CherryToast.error( + animationDuration: Durations.long1, + title: Text("Terjadi Kesalahan, silahkan coba lagi", + style: TextStyle( + color: Colors.black, + fontSize: 15, + )), + animationType: AnimationType.fromTop, + ).show(context); + }); + return Container(); + } + }, + ), + ); + }, + ); + } + + Future _showDialogNotLogin(String teks) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum $teks', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + }, + child: Text( + 'Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + ], + ), + ); + } + + Future<void> _showMessage() { + return showDialog<void>( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + if (state.state == cartProvider.ResultState.loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 1, + color: primaryColor, + ), + ); + } else if (state.state == cartProvider.ResultState.succes) { + return Align( + alignment: Alignment.topCenter, + child: Material( + type: MaterialType.transparency, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, 2), + color: Colors.black26, + ) + ], + ), + child: Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + onPressed: () { + Navigator.of(context).pop(); + }, + icon: Icon( + Icons.close, + color: Theme.of(context) + .colorScheme + .onBackground, + size: getProportionateScreenWidth(15), + ), + ), + ], + ), + Icon( + Icons.check_rounded, + size: getProportionateScreenWidth(40), + color: eightColor, + ), + SizedBox(height: getProportionateScreenHeight(15)), + Text( + 'Berhasil menambahkan kursus ke keranjang', + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + SizedBox(height: getProportionateScreenHeight(4)), + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pushReplacement( + CustomNavigator( + child: CartPage( + idcourse: widget.idcourse, + ), + ), + ); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontWeight: reguler, + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(18)), + ], + ); + }, + ), + ), + ), + ); + } else if (state.state == cartProvider.ResultState.failed) { + return Align( + alignment: Alignment.center, + child: Material( + type: MaterialType.transparency, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, 2), + color: Colors.black26, + ) + ], + ), + child: Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: getProportionateScreenHeight(20)), + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(13)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Kursus ini sudah berada dalam keranjang', + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(20)), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pushReplacement( + CustomNavigator( + child: CartPage( + idcourse: widget.idcourse, + ), + ), + ); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + SizedBox( + width: getProportionateScreenWidth(10)), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ); + }, + ), + ), + ), + ), + ); + } else { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Center( + child: Text('Terjadi Kesalahan'), + )); + } + }); + }, + ); + } + + Future<void> _showMessageCart() { + return showDialog<void>( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + if (state.state == cartProvider.ResultState.loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 1, + color: primaryColor, + ), + ); + } else if (state.state == cartProvider.ResultState.succes) { + return Align( + alignment: Alignment.center, + child: Material( + type: MaterialType.transparency, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, 2), + color: Colors.black26, + ) + ], + ), + child: Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: getProportionateScreenHeight(20)), + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(13)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Kursus ini sudah berada dalam keranjang', + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(20)), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pushReplacement( + CustomNavigator( + child: CartPage( + idcourse: widget.idcourse, + ), + ), + ); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + SizedBox( + width: getProportionateScreenWidth(10)), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ); + }, + ), + ), + ), + ), + ); + } else if (state.state == cartProvider.ResultState.failed) { + return SizedBox.shrink(); + } else { + return Align( + alignment: Alignment.center, + child: Material( + type: MaterialType.transparency, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, 2), + color: Colors.black26, + ) + ], + ), + child: Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: getProportionateScreenHeight(20)), + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(13)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Kursus ini sudah berada dalam keranjang', + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(20)), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pushReplacement( + CustomNavigator( + child: CartPage( + idcourse: widget.idcourse, + ), + ), + ); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + SizedBox( + width: getProportionateScreenWidth(10)), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ); + }, + ), + ), + ), + ), + ); + } + }); + }, + ); + } + + handleNotLogin() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + _showMessage(); + await Provider.of<cartProvider.CartProvider>(context, listen: false) + .addCart(int.parse(widget.idcourse)); + await Provider.of<cartsProvider.CartsProvider>(context, listen: false) + .getCarts(); + // await Provider.of<WishlistPostProvider>(context, listen: false) + // .addWishlist(int.parse(widget.idcourse)); + // await Provider.of<wishlistProvider.WishlistProvider>(context, + // listen: false) + // .getWishlist(); + // cartsDatabaseProvider.setCarts(id); + } else { + String teks = 'menambahkan ke keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginWishlistNotExist() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + _showMessage(); + await Provider.of<cartProvider.CartProvider>(context, listen: false) + .addCart(int.parse(widget.idcourse)); + await Provider.of<cartsProvider.CartsProvider>(context, listen: false) + .getCarts(); + } else { + String teks = 'menambahkan ke keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginBuy( + {required String title, + required String price, + required String discountPrice, + required String instructor, + required String imageUrl}) async { + var token = await UsersInfo().getToken(); + bool isInPendingPayment = await _isCourseInPendingPayment(widget.idcourse); + if (isInPendingPayment) { + CherryToast.error( + title: Text("Kursus ini sedang dalam proses pembayaran."), + animationType: AnimationType.fromTop, + animationDuration: Durations.medium1, + ).show(context); + return; + } + + if (token != null || Condition.loginFirebase == true) { + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .clear(); + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .addOrder( + id: widget.idcourse, + title: title, + price: price, + discountPrice: discountPrice, + imageUrl: imageUrl, + instructor: instructor, + ); + int total; + if (discountPrice == "0") { + total = int.parse(price) < 50000 + ? int.parse(price) + 5000 + : int.parse(price); + } else { + total = int.parse(discountPrice) < 50000 + ? int.parse(discountPrice) + 5000 + : int.parse(discountPrice); + } + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .getTotalPrice(total.toString()); + selectedTotalPrice.selectedSubTotal = price; + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: CheckoutCartPage( + idCart: idCarts, + potonganKupon: 0, + discountHarga: discountPrice == "0" + ? int.parse(price) + : int.parse(discountPrice), + isCart: true, + isDetailCourse: true, + ), + ), + ); + } else { + String teks = 'membeli kursus'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginFree( + String? id, + String? title, + String? thumb, + String? instr, + ) async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + if (await pay.freeCourse(int.parse(widget.idcourse))) { + // await Provider.of<myCourseProvider.MyCourseProvider>(context, + // listen: false) + // .getMyCourse(); + // await Provider.of<DetailCourseProvider>(context, listen: false) + // .getDetailCourseLogin(widget.idcourse); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => SuccessFreeCourse( + // id: id, + // thumbnail: thumb, + // title: title, + // instructor: instr, + // ))); + navigateAndRefresh( + thumb.toString(), title.toString(), instr.toString()); + } else { + showNotifDialog(context); + } + } else { + String teks = 'memiliki kursus ini'; + return _showDialogNotLogin(teks); + } + } + + Widget freeBottomNav( + String? courseId, + String? instructor, + String? instructorId, + String? thumbnail, + String? title, + String? statuscourse, + String isadmin) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + boxShadow: [ + BoxShadow( + color: baruTexthitam.withOpacity(0.1), + offset: Offset(0, 1), + blurRadius: 9, + spreadRadius: 6, + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if ('$instructorId' == '$iDuser') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + print('user nya sama'), + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Tidak bisa membeli kursus milik anda sendiri", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }, + ), + ), + ); + }, + ) + else if (isadmin == 'admin') + GestureDetector( + onTap: () { + print('disabled'); + }, + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }, + ), + ), + ) + else if (statuscourse == 'private') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + print('disabled private'), + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, // your tap handler moved here + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }, + ), + ), + ); + }, + ) + else if (statuscourse != 'active') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + print('disabled ini'), + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }, + ), + ), + ); + }, + ) + else + GestureDetector( + onTap: () => + handleNotLoginFree(courseId, title, thumbnail, instructor), + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }, + ), + ), + ), + ], + ), + ); + } + + Widget playCourseNav(String? courseId, String? instructor, + String? thumbnail, String? title) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + boxShadow: [ + BoxShadow( + color: baruTexthitam.withOpacity(0.1), + offset: Offset(0, 1), + blurRadius: 9, + spreadRadius: 6, + ), + ], + ), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => + lessonCourseProvider.LessonCourseProvider( + lessonCourseService: LessonCourseService(), + id: int.parse(courseId ?? '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), id: courseId ?? '1'), + ) + ], + child: PlayCourse( + judul: title ?? '', + instruktur: instructor ?? '', + thumbnail: thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + courseeid: courseId ?? '', + ), + ), + ), + ); + }, + child: Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(110), + vertical: getProportionateScreenHeight(10), + ), + decoration: BoxDecoration( + color: primaryColor, borderRadius: BorderRadius.circular(8)), + child: Center( + child: Text( + 'Lanjutkan Belajar', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ), + ); + } + + Widget bottomNav( + {required String discountPrice, + required String idCourse, + required String title, + required String price, + required String instructor, + required String? instructorId, + required String imageUrl, + required String statuscourse, + required String isadmin}) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + boxShadow: [ + BoxShadow( + color: baruTexthitam.withOpacity(0.1), + offset: Offset(0, 1), + blurRadius: 9, + spreadRadius: 6, + ), + ], + ), + child: Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(15)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if ('$instructorId' == '$iDuser') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Anda Tidak bisa membeli kursus milik anda sendiri", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else if (isadmin == 'admin' && statuscourse == 'prepublish') + GestureDetector( + onTap: () { + print('jadi admin'); + }, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ) + else if (cekstrAdmin == 'admin') + GestureDetector( + onTap: () { + print('jadi admin'); + }, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ) + else if (statuscourse == 'prepublish') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else if (statuscourse == 'private') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else if (statuscourse != 'active') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + print(statuscourse), + print('disiniii'), + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else + GestureDetector( + onTap: () { + print('masuk sini'); + print( + '4 ${selectedTotalPrice.selectedTotalPrice = int.parse(discountPrice)}'); + selectedTotalPrice.selectedTotalPrice = discountPrice == "0" + ? (int.parse(price) < 50000 + ? int.parse(price) + 5000 + : int.parse(price)) + : (int.parse(discountPrice) < 50000 + ? int.parse(discountPrice) + 5000 + : int.parse(discountPrice)); + if (numberFormat(discountPrice) == 'Rp. 0') { + print('asd'); + // Provider.of<paymentsProvider.PaymentsProvider>(context, + // listen: false) + // .zeroPayment(invoice, totalPrice!); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => DetailZeroPayment())); + handleNotLoginBuy( + title: title, + discountPrice: discountPrice, + imageUrl: imageUrl, + price: price, + instructor: instructor, + ); + } + handleNotLoginBuy( + title: title, + discountPrice: discountPrice, + imageUrl: imageUrl, + price: price, + instructor: instructor, + ); + }, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ), + + // Ini pembatasnya + + if (isadmin == 'admin') + Consumer<wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + return TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await {}, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: primaryColor, + width: getProportionateScreenWidth(1.3)), + ), + child: Center( + child: Text( + '+ Keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: primaryColor, + ), + ), + ), + ), + ); + }, + ); + }) + else if (statuscourse != 'active') + Consumer<wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + return TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: primaryColor, + width: getProportionateScreenWidth(1.3)), + ), + child: Center( + child: Text( + '+ Keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: primaryColor, + ), + ), + ), + ), + ); + }, + ); + }) + else if ('$instructorId' == '$iDuser') + Consumer<wishlistProvider.WishlistProvider>( + builder: (context, state, _) { + return TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Anda tidak dapat menambahkan ke keranjang kursus anda sendiri", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: primaryColor, + width: getProportionateScreenWidth(1.3)), + ), + child: Center( + child: Text( + '+ Keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: primaryColor, + ), + ), + ), + ), + ); + }, + ); + }) + else + Consumer<cartsProvider.CartsProvider>( + builder: (context, cartsState, _) { + bool isInCart = cartsState.data.contains(widget.idcourse); + return Consumer<wishlistProvider.WishlistProvider>( + builder: (context, wishlistState, _) { + return GestureDetector( + onTap: () async { + //validasi status payment + bool isInPendingPayment = await _isCourseInPendingPayment(widget.idcourse); + if (isInPendingPayment) { + CherryToast.error( + title: Text("Kursus ini sedang dalam proses pembayaran."), + animationType: AnimationType.fromTop, + animationDuration: Durations.medium1, + ).show(context); + return; + } + isInCart + ? _showMessageCart() + : wishlistState.data.contains(widget.idcourse) + ? handleNotLogin() + : handleNotLoginWishlistNotExist(); + }, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: isInCart + ? themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode + : Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + width: getProportionateScreenWidth(1.3)), + ), + child: Center( + child: Text( + '+ Keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: isInCart ? baruTextutih : themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ), + ), + ), + ); + }, + ); + // color: isInCart ? primaryColor : Color(0xff242424), + // icon: Icon( + // cartsState.data.contains(courseIdConvert) + // ? Icons.shopping_cart + // : Icons.shopping_cart, + // color: + // isInCart ? Color(0xff242424) : primaryColor, + // size: 20, + // ), + }, + ), + ], + ), + ), + ); + } + + return (cekstrAdmin == "") + ? Scaffold( + body: Container( + child: Center( + child: CircularProgressIndicator(), + ), + ), + ) + : SafeArea( + child: MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), + id: widget.idcourse, + ), + ), + ChangeNotifierProvider( + create: (context) => + detailRatingCourseProvider.DetailRatingCourseProvider( + courseService: CourseService(), id: widget.idcourse), + ), + ChangeNotifierProvider( + create: (context) => + sectionLessonCourseProvider.SectionLessonCourseProvider( + id: widget.idcourse, + sectionLessonService: SectionLessonService()), + ), + ], + child: Consumer<DetailCourseProvider>( + builder: (context, state, _) { + cekadminbaru(); + if (state.state == ResultState.Loading) { + return Scaffold( + body: Container( + child: Center( + child: CircularProgressIndicator(), + ), + ), + ); + } else if (state.state == ResultState.HasData) { + var detailCourse = state.result!.data[0][0]; + if (detailCourse.isMine == 0) { + isowned = false; + } else if (detailCourse.isMine == 1) { + isowned = true; + } + return Scaffold( + body: Stack( + children: [ + ListView( + controller: _scrollController, + physics: ScrollPhysics(), + shrinkWrap: true, + children: [ + Header( + dataDetailCourseModel: detailCourse, + ), + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => + instructorProvider.InstructorProvider( + instructorService: InstructorService(), + id: int.parse(detailCourse.instructorId!), + ), + ), + ChangeNotifierProvider( + create: (context) => TabProvider()), + ], + child: CustomTabBar( + dataDetailCourseModel: detailCourse, + totalDuration: detailCourse.totalDuration, + bio: detailCourse.bio, + instructor: detailCourse.instructor, + fotoProfile: detailCourse.fotoProfile, + rating: detailCourse.rating[0].avgRating + .toString(), + review: detailCourse.rating[0].totalReview, + totalLesson: detailCourse.totalLesson, + totalStudent: detailCourse.totalStudents, + headline: detailCourse.headlineInstructor, + resoaktifitas: widget.resoaktifitas, + idCategory: + detailCourse.breadcrumbs.idCategory, + ), + ), + ], + ), + AppBarHeader( + idcourse: widget.idcourse, + color: appBarColor, + ), + ], + ), + bottomNavigationBar: + (detailCourse.isMine == 1 || isowned == 1) + ? playCourseNav( + detailCourse.id, + detailCourse.instructor, + detailCourse.thumbnail, + detailCourse.title, + ) + : (detailCourse.isFreeCourse == '1') + ? freeBottomNav( + detailCourse.id, + detailCourse.instructor, + detailCourse.instructorId, + detailCourse.thumbnail, + detailCourse.title, + detailCourse.status_course, + cekstrAdmin) + : bottomNav( + discountPrice: (widget.isPromo == true) + ? detailCourse.promoPrice ?? ' ' + : detailCourse.discountPrice ?? ' ', + idCourse: detailCourse.id, + instructor: detailCourse.instructor!, + instructorId: detailCourse.instructorId!, + price: detailCourse.price!, + title: detailCourse.title!, + imageUrl: detailCourse.thumbnail ?? '', + statuscourse: + detailCourse.status_course ?? '', + isadmin: cekstrAdmin), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return const Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('500 Internal Server Error :('), + ], + ), + ), + ); + } else { + return const Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Terjadi kesalahan, sebaiknya hati-hati'), + ], + ), + ), + ); + } + }, + ), + ), + ); + } +} diff --git a/lib/screens/detail_course/detail_course_voucher_screen.dart b/lib/screens/detail_course/detail_course_voucher_screen.dart new file mode 100644 index 0000000..cf9ba0c --- /dev/null +++ b/lib/screens/detail_course/detail_course_voucher_screen.dart @@ -0,0 +1,1592 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/tab_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/checkout/checkout_cart_coupon_page.dart'; +import 'package:initial_folder/screens/checkout/detail_zero_payment.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/my_course/success_free_course.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/screens/detail_course/components/custom_tab_bar.dart'; +import 'package:initial_folder/screens/detail_course/components/header.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/instructor_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/services/section_lesson_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; +import '../../services/user_info_service.dart'; +import 'package:initial_folder/providers/cart_provider.dart' as cartProvider; +import 'package:initial_folder/providers/carts_provider.dart' as cartsProvider; +import 'package:initial_folder/providers/order_provider.dart' as orderProvider; +import 'package:initial_folder/providers/detail_rating_course_provider.dart' + as detailRatingCourseProvider; +import 'package:initial_folder/providers/instructor_provider.dart' + as instructorProvider; +import 'package:initial_folder/providers/lesson_course_provider.dart' + as lessonCourseProvider; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; +import 'package:initial_folder/providers/payments_provider.dart' + as paymentsProvider; +import 'package:initial_folder/providers/section_lesson_course_provider.dart' + as sectionLessonCourseProvider; + +class DetailVoucherScreen extends StatefulWidget { + const DetailVoucherScreen({ + Key? key, + required this.idcourse, + this.coupon, + this.resoaktifitas, + }) : super(key: key); + + final String idcourse; + final String? coupon; + final String? resoaktifitas; + static String routeName = "/course_detail"; + + @override + _DetailVoucherScreenState createState() => _DetailVoucherScreenState(); +} + +class _DetailVoucherScreenState extends State<DetailVoucherScreen> { + late String cekstrAdmin = ''; + late DataDetailCourseModel dataDetailCourseModel; + late int? iDuser; + bool _isAdminChecked = false; + ScrollController _scrollController = ScrollController(); + Color appBarColor = Colors.transparent; + + @override + void initState() { + super.initState(); + _scrollController.addListener(() { + if (_scrollController.position.pixels > 50) { + setState(() { + appBarColor = Theme.of(context).colorScheme.background; + }); + } else { + setState(() { + appBarColor = Colors.transparent; + }); + } + }); + cekadminbaru(); + getIDuser(); + } + + void getIDuser() { + UsersInfo().getIdUser().then((value) => { + setState(() { + iDuser = value; + }), + }); + } + + void cekadminbaru() { + if (!_isAdminChecked) { + UserInfoService().getDataDiriADMIN().then((value) => { + if (value == 401) + { + setState(() { + cekstrAdmin = 'admin'; + }), + } + else + { + setState(() { + cekstrAdmin = 'user'; + }), + }, + _isAdminChecked = true, + }); + } + } + + @override + Widget build(BuildContext context) { + final selectedTotalPrice = Provider.of<TotalPriceProvider>(context); + final typeCoupon = Provider.of<TotalPriceProvider>(context).typeCoupon; + List<String> idCarts = []; + SizeConfig().init(context); + paymentsProvider.PaymentsProvider pay = + Provider.of<paymentsProvider.PaymentsProvider>(context); + final finalPriceCoupon = + Provider.of<TotalPriceProvider>(context).finalPriceCoupon; + final potonganKupon = Provider.of<TotalPriceProvider>(context) + .potonganKupon + ?.round() + .toString() ?? + '0'; + + showNotifDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, child) { + if (state.state == paymentsProvider.ResultState.gagal) { + return Container( + height: getProportionateScreenHeight(40), + width: getProportionateScreenWidth(15), + child: Center( + child: Text( + 'Anda sudah memiliki kursus ini', + style: primaryTextStyle.copyWith(fontSize: 12), + textAlign: TextAlign.center, + ), + ), + ); + } else { + Future.delayed(Duration.zero, () { + Navigator.pop(context); + CherryToast.error( + animationDuration: Durations.long1, + title: Text("Terjadi Kesalahan, silahkan coba lagi", + style: TextStyle( + color: Colors.black, + fontSize: 15, + )), + animationType: AnimationType.fromTop, + ).show(context); + }); + return Container(); + } + }, + ), + ); + }, + ); + } + + Future _showDialogNotLogin(String teks) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(12, 20, 12, 1), + content: Text( + 'Mohon login terlebih dahulu sebelum $teks', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + }, + child: Text( + 'Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + ], + ), + ); + } + + Future<void> _showMessage() { + return showModalBottomSheet<void>( + context: context, + builder: (BuildContext context) { + return Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + if (state.state == cartProvider.ResultState.loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 1, + color: primaryColor, + ), + ); + } else if (state.state == cartProvider.ResultState.succes) { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Column( + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.of(context).pop(); + }, + icon: Icon( + Icons.close, + color: secondaryColor, + size: 15, + )), + ], + ), + Icon( + Icons.check_rounded, + size: 40, + color: eightColor, + ), + SizedBox(height: getProportionateScreenWidth(15)), + Text( + 'Berhasil menambahkan kursus ke keranjang', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 4, + letterSpacing: 0.5), + ), + SizedBox(height: getProportionateScreenWidth(4)), + TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric(vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pushReplacement( + CustomNavigator( + child: CartPage( + idcourse: widget.idcourse, + ), + ), + ); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + letterSpacing: 0.5, + color: primaryColor), + ), + ), + ], + ), + ); + } else if (state.state == cartProvider.ResultState.failed) { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Column( + children: [ + Row( + children: [ + IconButton( + onPressed: () { + Navigator.of(context).pop(); + }, + icon: Icon( + Icons.close, + color: secondaryColor, + size: 15, + ), + ), + ], + ), + Icon( + Icons.close, + size: 40, + color: Colors.red, + ), + SizedBox( + height: getProportionateScreenWidth(15), + ), + Text( + 'Berhasil menghapus kursus dari keranjang', + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 4, + letterSpacing: 0.5), + ), + SizedBox( + height: getProportionateScreenWidth(4), + ), + TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric(vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pushReplacement( + CustomNavigator( + child: CartPage( + idcourse: widget.idcourse, + ), + ), + ); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + letterSpacing: 0.5, + color: primaryColor), + ), + ), + ], + ), + ); + } else { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Center( + child: Text('Terjadi Kesalahan'), + )); + } + }); + }, + ); + } + + Future<void> _showMessageCart() { + return showDialog<void>( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + if (state.state == cartProvider.ResultState.loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 1, + color: primaryColor, + ), + ); + } else if (state.state == cartProvider.ResultState.succes) { + return Align( + alignment: Alignment.center, + child: Material( + type: MaterialType.transparency, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: BorderRadius.circular(5), + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, 2), + color: Colors.black26, + ) + ], + ), + child: Consumer<cartProvider.CartProvider>( + builder: (context, state, _) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: getProportionateScreenHeight(20)), + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(13)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Kursus ini sudah berada dalam keranjang', + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenHeight(20)), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + TextButton( + style: ButtonStyle( + overlayColor: + MaterialStateProperty.all(sixColor), + padding: MaterialStateProperty.all( + EdgeInsets.symmetric( + vertical: 1, horizontal: 1), + ), + ), + onPressed: () { + Navigator.of(context).pushReplacement( + CustomNavigator( + child: CartPage( + idcourse: widget.idcourse, + ), + ), + ); + }, + child: Text( + 'Lihat keranjang', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + color: primaryColor, + fontWeight: reguler, + ), + ), + ), + SizedBox( + width: getProportionateScreenWidth(10)), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ); + }, + ), + ), + ), + ), + ); + } else if (state.state == cartProvider.ResultState.failed) { + return SizedBox.shrink(); + } else { + return Container( + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + blurRadius: 10, + offset: Offset(0, -1), + ) + ], + borderRadius: BorderRadius.vertical( + top: Radius.circular(10), + ), + color: Color(0xff242424), + ), + height: getProportionateScreenHeight(200), + child: Center( + child: Text('Terjadi Kesalahan'), + )); + } + }); + }, + ); + } + + handleNotLogin() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + _showMessage(); + await Provider.of<cartProvider.CartProvider>(context, listen: false) + .addCart(int.parse(widget.idcourse)); + await Provider.of<cartsProvider.CartsProvider>(context, listen: false) + .getCarts(); + } else { + String teks = 'menambahkan ke keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginWishlistNotExist() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + _showMessage(); + await Provider.of<cartProvider.CartProvider>(context, listen: false) + .addCart(int.parse(widget.idcourse)); + await Provider.of<cartsProvider.CartsProvider>(context, listen: false) + .getCarts(); + } else { + String teks = 'menambahkan ke keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginBuy( + {required String title, + required String price, + required String discountPrice, + required String instructor, + required String imageUrl}) async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .clear(); + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .addOrder( + id: widget.idcourse, + title: title, + price: finalPriceCoupon != 0 + ? finalPriceCoupon! < 50000 + ? (finalPriceCoupon + 5000).toString() + : finalPriceCoupon.toString() + : finalPriceCoupon.toString(), + discountPrice: finalPriceCoupon.toString(), + imageUrl: imageUrl, + instructor: instructor, + ); + int total; + if (discountPrice == "0") { + total = int.parse(price) < 50000 + ? int.parse(price) + 5000 + : int.parse(price); + } else { + total = int.parse(discountPrice) < 50000 + ? int.parse(discountPrice) + 5000 + : int.parse(discountPrice); + } + Provider.of<orderProvider.OrderProvider>(context, listen: false) + .getTotalPrice(total.toString()); + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: CheckoutCartCouponPage( + idCart: idCarts, + potonganKupon: int.parse(potonganKupon.toString()), + discountHarga: discountPrice == "0" + ? int.parse(price) + : int.parse(discountPrice), + ), + ), + ); + } else { + String teks = 'membeli kursus'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginFree( + String? id, + String? title, + String? thumb, + String? instr, + ) async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + if (await pay.freeCourseCoupon( + int.parse(widget.idcourse), widget.coupon!)) { + await Provider.of<myCourseProvider.MyCourseProvider>(context, + listen: false) + .getMyCourse(); + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => SuccessFreeCourse( + id: id, + thumbnail: thumb, + title: title, + instructor: instr, + ), + ), + ); + } else { + showNotifDialog(context); + } + } else { + String teks = 'memiliki kursus ini'; + return _showDialogNotLogin(teks); + } + } + + Widget freeBottomNav( + String? courseId, + String? instructor, + String? instructorId, + String? thumbnail, + String? title, + String? statuscourse, + String isadmin) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + boxShadow: [ + BoxShadow( + color: baruTexthitam.withOpacity(0.1), + offset: Offset(0, 1), + blurRadius: 9, + spreadRadius: 6, + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if ('$instructorId' == '$iDuser') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + print('user nya sama'), + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Tidak bisa membeli kursus milik anda sendiri", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(123), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: fiveColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }), + ), + ), + ); + }, + ) + else if (isadmin == 'admin') + GestureDetector( + onTap: () { + print('disabled'); + }, + child: Container( + margin: + EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(123), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: fiveColor, borderRadius: BorderRadius.circular(5)), + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }), + ), + ), + ) + else if (statuscourse == 'private') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + print('disabled private'), + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(123), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: fiveColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }), + ), + ), + ); + }, + ) + else if (statuscourse != 'active') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + print('disabled ini'), + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(123), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: fiveColor, + borderRadius: BorderRadius.circular(5)), + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(113), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(8)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }), + ), + ), + ); + }, + ) + else + GestureDetector( + onTap: () => + handleNotLoginFree(courseId, title, thumbnail, instructor), + child: Center( + child: Consumer<paymentsProvider.PaymentsProvider>( + builder: (context, state, _) { + if (state.stateProcess == + paymentsProvider.Process.uninitialized) { + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + } else if (state.stateProcess == + paymentsProvider.Process.loading) { + return Container( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: eightColor, + strokeWidth: 2, + ), + ); + } + return Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Miliki Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ); + }, + ), + ), + ), + ], + ), + ); + } + + Widget playCourseNav(String? courseId, String? instructor, + String? thumbnail, String? title) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + boxShadow: [ + BoxShadow( + color: baruTexthitam.withOpacity(0.1), + offset: Offset(0, 1), + blurRadius: 9, + spreadRadius: 6, + ), + ], + ), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => + lessonCourseProvider.LessonCourseProvider( + lessonCourseService: LessonCourseService(), + id: int.parse(courseId ?? '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), id: courseId ?? '1'), + ) + ], + child: PlayCourse( + judul: title ?? '', + instruktur: instructor ?? '', + thumbnail: thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + courseeid: courseId ?? '', + ), + ), + ), + ); + }, + child: Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(110), + vertical: getProportionateScreenHeight(10), + ), + decoration: BoxDecoration( + color: primaryColor, borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Lanjutkan Belajar', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ), + ); + } + + Widget bottomNav( + {required String discountPrice, + required String idCourse, + required String title, + required String price, + required String instructor, + required String? instructorId, + required String imageUrl, + required String statuscourse, + required String isadmin}) { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + boxShadow: [ + BoxShadow( + color: baruTexthitam.withOpacity(0.1), + offset: Offset(0, 1), + blurRadius: 9, + spreadRadius: 6, + ), + ], + ), + child: Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(15)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if ('$instructorId' == '$iDuser') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Anda Tidak bisa membeli kursus milik anda sendiri", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else if (isadmin == 'admin' && statuscourse == 'prepublish') + GestureDetector( + onTap: () { + print('jadi admin'); + }, + child: Container( + margin: + EdgeInsets.only(right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(113), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(8)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ) + else if (cekstrAdmin == 'admin') + GestureDetector( + onTap: () { + print('jadi admin'); + }, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ) + else if (statuscourse == 'prepublish') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(113), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(8)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else if (statuscourse == 'private') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else if (statuscourse != 'active') + TapDebouncer( + cooldown: const Duration(milliseconds: 3500), + onTap: () async => await { + CherryToast.error( + animationDuration: Durations.long1, + title: Text( + "Kursus sedang dalam status\ntidak aktif", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context) + }, + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return GestureDetector( + onTap: onTap, + child: Container( + margin: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + width: getProportionateScreenWidth(113), + height: getProportionateScreenHeight(33), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(8)), + child: Center( + child: Text( + 'Segera Hadir', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ); + }, + ) + else + GestureDetector( + onTap: () { + print( + '4 ${selectedTotalPrice.selectedTotalPrice = int.parse(discountPrice)}'); + selectedTotalPrice.selectedTotalPrice = discountPrice == "0" + ? (int.parse(price) < 50000 + ? int.parse(price) + 5000 + : int.parse(price)) + : (int.parse(discountPrice) < 50000 + ? int.parse(discountPrice) + 5000 + : int.parse(discountPrice)); + if (numberFormat(discountPrice) == 'Rp. 0') { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DetailZeroPayment(), + ), + ); + handleNotLoginBuy( + title: title, + discountPrice: discountPrice, + imageUrl: imageUrl, + price: price, + instructor: instructor, + ); + } + handleNotLoginBuy( + title: title, + discountPrice: discountPrice, + imageUrl: imageUrl, + price: price, + instructor: instructor, + ); + }, + child: Container( + width: getProportionateScreenWidth(140), + height: getProportionateScreenHeight(40), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(10)), + child: Center( + child: Text( + 'Beli Sekarang', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + letterSpacing: 0.085, + color: baruTextutih, + ), + ), + ), + ), + ), + ], + ), + ), + ); + } + + return (cekstrAdmin == "") + ? Container( + child: Center( + child: CircularProgressIndicator(), + ), + ) + : SafeArea( + child: MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), + id: widget.idcourse, + ), + ), + ChangeNotifierProvider( + create: (context) => + detailRatingCourseProvider.DetailRatingCourseProvider( + courseService: CourseService(), id: widget.idcourse), + ), + ChangeNotifierProvider( + create: (context) => + sectionLessonCourseProvider.SectionLessonCourseProvider( + id: widget.idcourse, + sectionLessonService: SectionLessonService()), + ), + ], + child: Consumer<DetailCourseProvider>( + builder: (context, state, _) { + cekadminbaru(); + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + var detailCourse = state.result!.data[0][0]; + return Scaffold( + body: Stack( + children: [ + ListView( + controller: _scrollController, + physics: ScrollPhysics(), + shrinkWrap: true, + children: [ + Header( + dataDetailCourseModel: detailCourse, + coupon: widget.coupon, + ), + MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => + instructorProvider.InstructorProvider( + instructorService: InstructorService(), + id: int.parse(detailCourse.instructorId!), + ), + ), + ChangeNotifierProvider( + create: (context) => TabProvider()), + ], + child: CustomTabBar( + dataDetailCourseModel: detailCourse, + totalDuration: detailCourse.totalDuration, + bio: detailCourse.bio, + instructor: detailCourse.instructor, + fotoProfile: detailCourse.fotoProfile, + rating: detailCourse.rating[0].avgRating + .toString(), + review: detailCourse.rating[0].totalReview, + totalLesson: detailCourse.totalLesson, + totalStudent: detailCourse.totalStudents, + headline: detailCourse.headlineInstructor, + resoaktifitas: widget.resoaktifitas, + idCategory: + detailCourse.breadcrumbs.idCategory, + ), + ), + ], + ), + AppBarHeader( + idcourse: widget.idcourse, + color: appBarColor, + ), + ], + ), + bottomNavigationBar: (detailCourse.isMine == 1) + ? playCourseNav( + detailCourse.id, + detailCourse.instructor, + detailCourse.thumbnail, + detailCourse.title, + ) + : (detailCourse.isFreeCourse == '1' || + typeCoupon == "1") + ? freeBottomNav( + detailCourse.id, + detailCourse.instructor, + detailCourse.instructorId, + detailCourse.thumbnail, + detailCourse.title, + detailCourse.status_course, + cekstrAdmin) + : bottomNav( + discountPrice: + detailCourse.discountPrice ?? ' ', + idCourse: detailCourse.id, + instructor: detailCourse.instructor!, + instructorId: detailCourse.instructorId!, + price: detailCourse.price!, + title: detailCourse.title!, + imageUrl: detailCourse.thumbnail ?? '', + statuscourse: + detailCourse.status_course ?? '', + isadmin: cekstrAdmin), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return const Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Error 500: Terdapat kesalahan pada server'), + ], + ), + ), + ); + } else { + return const Scaffold( + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text('Terjadi kesalahan, sebaiknya hati-hati'), + ], + ), + ), + ); + } + }, + ), + ), + ); + } +} diff --git a/lib/screens/home/components/appBar/home_header.dart b/lib/screens/home/components/appBar/home_header.dart new file mode 100644 index 0000000..77e50a0 --- /dev/null +++ b/lib/screens/home/components/appBar/home_header.dart @@ -0,0 +1,236 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/notification_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import '../../../../size_config.dart'; +import '../notification.dart'; +import 'icon_btn_with_counter.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; + +class HomeHeader extends StatefulWidget { + const HomeHeader({ + Key? key, + }) : super(key: key); + + @override + State<HomeHeader> createState() => _HomeHeaderState(); +} + +class _HomeHeaderState extends State<HomeHeader> { + @override + void initState() { + super.initState(); + Future.delayed(Duration(seconds: 0), () async { + await Provider.of<NotificationProvider>(context, listen: false) + .getNotificationCount(); + }); + } + + @override + Widget build(BuildContext context) { + UserInfoProvider userInfoProvider = Provider.of<UserInfoProvider>(context); + FirebaseMessaging.onMessage.listen((event) async { + if (event.notification!.body!.isNotEmpty) { + await Provider.of<NotificationProvider>(context, listen: false) + .getNotificationCount(); + } + }); + + Future _showDialogNotLogin(String teks) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + elevation: 0.0, + contentPadding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20), + vertical: getProportionateScreenHeight(20), + ), + content: Text( + 'Mohon login terlebih dahulu sebelum $teks', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11)), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + }, + child: Text( + 'Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + ], + ), + ); + } + + handleNotLoginCart() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: CartPage(), + ), + ); + } else { + String teks = 'dapat mengakses keranjang'; + return _showDialogNotLogin(teks); + } + } + + handleNotLoginNotif() async { + var token = await UsersInfo().getToken(); + if (token != null || Condition.loginFirebase == true) { + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: Notifikasi(), + ), + ); + } else { + String teks = 'dapat mengakses notifikasi'; + return _showDialogNotLogin(teks); + } + } + + return Container( + height: getProportionateScreenHeight(60), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.only(left: getProportionateScreenWidth(23)), + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: 'Welcome ${userInfoProvider.fullName}\n', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black), + ), + TextSpan( + text: 'Let\'s\ Explore Course', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(16), + fontWeight: bold, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black), + ), + ], + ), + ), + ), + Row( + children: [ + Transform.scale( + origin: Offset(-11, 0), + scale: getProportionateScreenHeight(0.9), + child: Container( + padding: + EdgeInsets.only(right: getProportionateScreenWidth(10)), + child: Consumer<NotificationProvider>( + builder: (context, value, child) { + return IconBtnWithCounter( + icon: Theme.of(context).brightness == Brightness.dark + ? SvgPicture.asset( + "assets/icons/notification_dark.svg") + : SvgPicture.asset( + "assets/icons/notification.svg"), + numOfitem: + (Condition.loginEmail || Condition.loginFirebase) + ? value.notificationCount + : 0, + press: () { + handleNotLoginNotif(); + }, + ); + }, + ), + ), + ), + !Condition.loginEmail && !Condition.loginFirebase + ? Transform.scale( + origin: Offset(0, 0), + scale: getProportionateScreenHeight(1), + child: Container( + padding: EdgeInsets.fromLTRB( + getProportionateScreenHeight(3), + 0, + getProportionateScreenHeight(3), + 0), + child: IconBtnWithCounter( + numOfitem: 0, + icon: Theme.of(context).brightness == + Brightness.dark + ? SvgPicture.asset("assets/icons/cart_dark.svg") + : SvgPicture.asset("assets/icons/cart.svg"), + press: () => handleNotLoginCart(), + ), + ), + ) + : Transform.scale( + origin: Offset(0, 0), + scale: getProportionateScreenHeight(1), + child: Container( + padding: EdgeInsets.fromLTRB( + getProportionateScreenHeight(3), + 0, + getProportionateScreenHeight(3), + 0), + child: Consumer<CartsProvider>( + builder: (context, state, _) { + return IconBtnWithCounter( + numOfitem: state.result == null + ? 0 + : state.data.length, + icon: Theme.of(context).brightness == + Brightness.dark + ? SvgPicture.asset( + "assets/icons/cart_dark.svg") + : SvgPicture.asset("assets/icons/cart.svg"), + press: () => handleNotLoginCart(), + ); + }, + ), + ), + ), + Padding( + padding: + EdgeInsets.only(left: SizeConfig.blockHorizontal! * 4), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/home/components/appBar/icon_btn_with_counter.dart b/lib/screens/home/components/appBar/icon_btn_with_counter.dart new file mode 100644 index 0000000..7c0f9c8 --- /dev/null +++ b/lib/screens/home/components/appBar/icon_btn_with_counter.dart @@ -0,0 +1,63 @@ +// import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/theme.dart'; + +class IconBtnWithCounter extends StatelessWidget { + const IconBtnWithCounter({ + Key? key, + required this.icon, + this.numOfitem = 0, + required this.press, + }) : super(key: key); + + final Widget icon; + final int numOfitem; + final GestureTapCallback press; + + @override + Widget build(BuildContext context) { + return IconButton( + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, + onPressed: press, + visualDensity: VisualDensity(horizontal: -4.0, vertical: -4.0), + padding: EdgeInsets.zero, + icon: Stack( + clipBehavior: Clip.none, + children: [ + Container( + height: 26, + width: 26, + child: icon, + ), + if (numOfitem != 0) + Positioned( + top: 0, + right: -2, + child: Container( + height: 14, + width: 16, + decoration: BoxDecoration( + color: Color(0xffCD2228), + shape: BoxShape.circle, + // border: Border.all(width: 1.5, color: Colors.red), + ), + child: Center( + child: Text( + "$numOfitem", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 8, + height: 1.3, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/home/components/body_comp/beginning.dart b/lib/screens/home/components/body_comp/beginning.dart new file mode 100644 index 0000000..f347dad --- /dev/null +++ b/lib/screens/home/components/body_comp/beginning.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import '../../../../theme.dart'; +import '../../../../size_config.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; + +class Begin extends StatelessWidget { + const Begin({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + //EmailProvider emailProvider = Provider.of<EmailProvider>(context); + final user = FirebaseAuth.instance.currentUser; + return Row( + children: <Widget>[ + Column( + children: <Widget>[ + Container( + height: getProportionateScreenWidth(60), + width: getProportionateScreenWidth(60), + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + //border: Border.all(width: 2.0, color: Colors.white), + image: (Condition.loginFirebase == true) + ? DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(user!.photoURL!)) + : DecorationImage( + image: + AssetImage('assets/images/Profile Image.png'))), + ), + ], + ), + SizedBox(width: getProportionateScreenWidth(24)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: <Widget>[ + //SizedBox(height: getProportionateScreenWidth(4)), + Row( + children: <Widget>[ + Text( + "Hai,", + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + color: tenthColor, + ), + textAlign: TextAlign.left, + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(7)), + Row( + children: <Widget>[ + Text( + 'Mau Upgrade Skill Apa?', + style: secondaryTextStyle.copyWith( + color: tenthColor, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + textAlign: TextAlign.left, + ), + ], + ), + ], + ), + ], + ); + } +} diff --git a/lib/screens/home/components/body_comp/carousel.dart b/lib/screens/home/components/body_comp/carousel.dart new file mode 100644 index 0000000..d80a486 --- /dev/null +++ b/lib/screens/home/components/body_comp/carousel.dart @@ -0,0 +1,218 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/banners_model.dart'; +import 'package:initial_folder/providers/banners_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/terms_and_privacy.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../../../size_config.dart'; + +class CarouselWithIndicatorDemo extends StatefulWidget { + @override + State<StatefulWidget> createState() { + return _CarouselWithIndicatorState(); + } +} + +class _CarouselWithIndicatorState extends State<CarouselWithIndicatorDemo> { + int _current = 0; + + @override + Widget build(BuildContext context) { + Widget listBannerPicture(BannersModel listBanner) { + return InkWell( + onTap: () { + String? idCourse; + if (listBanner.courseId.isNotEmpty) { + idCourse = listBanner.courseId; + } else { + if (listBanner.url!.isNotEmpty) { + if (listBanner.url! + .contains('https://vocasia-v4-develop.vercel.app/')) { + List<String> urlList = listBanner.url!.split('/'); + try { + idCourse = urlList.last; + } on StateError { + idCourse = '0'; + } + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: idCourse ?? '0', + ), + ), + ); + } else { + Navigator.of(context, rootNavigator: true).push( + MaterialPageRoute( + builder: (context) => TermsAndCondition( + // url: 'https://vocasia.id/home/contact', + url: listBanner.url!, + ), + ), + ); + } + } + } + }, + splashColor: Colors.white10, + child: Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(10)), + child: AspectRatio( + aspectRatio: 2, + child: CachedNetworkImage( + fadeInDuration: Duration(microseconds: 1), + imageUrl: listBanner.img.toString(), + fit: BoxFit.contain, + placeholder: (context, url) => Shimmer( + child: Container( + decoration: BoxDecoration( + color: ninthColor, + borderRadius: BorderRadius.circular(5), + ), + ), + gradient: LinearGradient( + stops: [0.2, 0.5, 0.6], + colors: [ninthColor, fourthColor, ninthColor])), + errorWidget: (context, url, error) => Icon(Icons.error), + ), + ), + ), + ), + ); + } + + return Container( + child: Consumer<BannersProvider>(builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Padding( + padding: const EdgeInsets.all(18.0), + child: Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.white), + height: 210, + width: 50, + )), + ); + } else if (state.state == ResultState.HasData) { + return Container( + child: Column( + children: [ + CarouselSlider( + items: state.result + .map((banner) => listBannerPicture(banner)) + .toList(), + options: CarouselOptions( + height: getProportionateScreenHeight(200), + disableCenter: true, + autoPlay: true, + autoPlayInterval: Duration(seconds: 10), + enlargeCenterPage: true, + viewportFraction: 1, + aspectRatio: 2, + onPageChanged: (index, reason) { + setState(() { + _current = index; + }); + }), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: state.result.map((url) { + int index = state.result.indexOf(url); + return Container( + width: getProportionateScreenWidth(6.0), + height: getProportionateScreenWidth(6.0), + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(3.0), + vertical: getProportionateScreenWidth(16)), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _current == index ? thirdColor : fourthColor, + ), + ); + }).toList()), + ], + ), + ); + } else if (state.state == ResultState.NoData) { + final defaultBanners = [ + 'assets/images/deffault_banner.png', + 'assets/images/default_banner_2.png' + ]; + + return Column( + children: [ + CarouselSlider.builder( + itemCount: defaultBanners.length, + itemBuilder: (context, index, realIndex) { + return Image.asset( + defaultBanners[index], + width: double.infinity, + ); + }, + options: CarouselOptions( + height: getProportionateScreenHeight(200), + autoPlay: true, + autoPlayInterval: Duration(seconds: 10), + enlargeCenterPage: true, + viewportFraction: 1, + aspectRatio: 2, + onPageChanged: (index, reason) { + setState(() { + _current = index; + }); + }, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(defaultBanners.length, (index) { + return Container( + width: getProportionateScreenWidth(6.0), + height: getProportionateScreenWidth(6.0), + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(3.0), + vertical: getProportionateScreenWidth(16), + ), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: _current == index ? thirdColor : fourthColor, + ), + ); + }), + ), + ], + ); + } else if (state.state == ResultState.Error) { + return Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(76), + top: getProportionateScreenHeight(45), + ), + child: Container( + width: getProportionateScreenWidth(480), + height: getProportionateScreenHeight(100), + color: Colors.transparent, + child: Text('Internet lemah, mohon muat ulang'), + ), + ); + } else { + return Center(child: Text('')); + } + }), + ); + } +} diff --git a/lib/screens/home/components/body_comp/certificate_voucher.dart b/lib/screens/home/components/body_comp/certificate_voucher.dart new file mode 100644 index 0000000..d78e218 --- /dev/null +++ b/lib/screens/home/components/body_comp/certificate_voucher.dart @@ -0,0 +1,164 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/screens/certificate/certificate.dart'; +import 'package:initial_folder/screens/coupon/coupon_page.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator_bottom.dart'; + +class CertificateVoucher extends StatelessWidget { + const CertificateVoucher({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + Future _showDialogNotLogin() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + elevation: 0.0, + contentPadding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20), + vertical: getProportionateScreenHeight(20), + ), + content: Text( + 'Mohon login terlebih dahulu sebelum menukarkan kupon', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11)), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Batal', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor), + ), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + GestureDetector( + onTap: () => Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false), + child: Text('Login', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ], + ), + ); + } + + return IntrinsicHeight( + child: Column( + children: [ + SizedBox(height: getProportionateScreenHeight(20)), + Container( + height: getProportionateScreenHeight(44), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: -5, + blurRadius: 10, + offset: Offset(0, 3), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context, rootNavigator: true).push( + CustomNavigatorBottom( + child: Certificate(), + ), + ); + }, + child: Row( + children: [ + SvgPicture.asset( + "assets/icons/certificate.svg", + color: Theme.of(context).colorScheme.onPrimary, + width: getProportionateScreenWidth(13), + ), + SizedBox(width: getProportionateScreenWidth(5)), + Text( + 'Sertifikat', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + ), + SizedBox(width: getProportionateScreenWidth(40)), + Container( + height: getProportionateScreenHeight(18), + child: VerticalDivider( + color: Theme.of(context).colorScheme.onPrimary, + thickness: 1), + ), + SizedBox(width: getProportionateScreenWidth(40)), + GestureDetector( + onTap: () { + if (Condition.loginEmail || Condition.loginFirebase) { + showModalBottomSheet( + elevation: 0, + backgroundColor: + Theme.of(context).colorScheme.background, + context: context, + builder: (context) => ClipRect(child: CouponPage()), + isScrollControlled: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + ); + } else if (!Condition.loginEmail || + !Condition.loginFirebase) { + _showDialogNotLogin(); + } + }, + child: Row( + children: [ + SvgPicture.asset( + "assets/icons/voucher.svg", + color: Theme.of(context).colorScheme.onPrimary, + width: getProportionateScreenWidth(13), + ), + SizedBox(width: getProportionateScreenWidth(5)), + Text( + 'Voucher', + textAlign: TextAlign.start, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/screens/home/components/body_comp/course_by_category.dart b/lib/screens/home/components/body_comp/course_by_category.dart new file mode 100644 index 0000000..b11c423 --- /dev/null +++ b/lib/screens/home/components/body_comp/course_by_category.dart @@ -0,0 +1,286 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/course_by_category_provider.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar_filter.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/latest_course.dart'; +import 'package:initial_folder/screens/home/components/body_comp/list_of_categories.dart'; +import 'package:initial_folder/screens/home/components/body_comp/populer_course.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/services/course_by_category_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; + +class CourseByCategory extends StatefulWidget { + CourseByCategory({ + Key? key, + required this.name, + required this.categoryId, + required this.subId, + this.homeCategories, + }) : super(key: key); + + final String name; + final String categoryId; + final String subId; + final bool? homeCategories; + + @override + State<CourseByCategory> createState() => _CourseByCategoryState(); +} + +class _CourseByCategoryState extends State<CourseByCategory> { + bool _isExpanded = false; + bool _buttonPressed = false; + final ScrollController _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + controller: _scrollController, + child: ChangeNotifierProvider<CourseByCategoryProvider>( + create: (context) => CourseByCategoryProvider( + courseByCategoryService: CourseByCategoryService(), + id: widget.categoryId, + subId: widget.subId, + fetchBySubcategory: false, + ), + child: Container( + child: Consumer<CourseByCategoryProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(300)), + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ), + ); + } else if (state.state == ResultState.HasData) { + return Stack( + children: [ + GestureDetector( + child: Container( + color: Colors.transparent, + ), + ), + GestureDetector( + onTap: () { + setState(() { + _isExpanded = !_isExpanded; + _buttonPressed = !_buttonPressed; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppBarHeader(), + SizedBox(height: getProportionateScreenHeight(23)), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(19)), + child: Text( + '${widget.name.replaceAll('&', '&')}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(20), + letterSpacing: -0.5, + fontWeight: FontWeight.w600, + ), + textAlign: TextAlign.start, + ), + ), + if (widget.homeCategories == null) + SizedBox( + height: getProportionateScreenHeight(65)), + if (widget.homeCategories == null) + PopulerCourse(text: 'Populer'), + if (widget.homeCategories == null) + LatestCourse(text: "New Release"), + if (widget.homeCategories == null) + SizedBox( + height: getProportionateScreenHeight(15)), + if (widget.homeCategories != null) + SizedBox( + height: getProportionateScreenHeight(25)), + if (widget.homeCategories == null) + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(16), + bottom: getProportionateScreenHeight(28), + ), + child: Text( + "${widget.name}", + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + ), + GridView.builder( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(21), + left: getProportionateScreenWidth(7), + bottom: getProportionateScreenHeight(18), + ), + physics: ScrollPhysics(), + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.8 / 4, + crossAxisSpacing: 16, + mainAxisSpacing: 14, + ), + itemCount: state.result.length, + itemBuilder: (context, index) { + var courses = state.result[index]; + int price = int.tryParse( + courses.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse(courses + .discountPrice + .replaceAll('.', '')) ?? + 0; + + int calculatedPrice = + (courses.discountPrice != '0') + ? price - discountPrice + : price; + String displayedPrice = (calculatedPrice == 0) + ? courses.price + : calculatedPrice.toString(); + + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(13)), + child: ProductCard( + totalDiscount: courses.totalDiscount ?? 0, + students: courses.students ?? '0', + id: courses.idCourse, + thumbnail: courses.thumbnail ?? + 'https://vocasia.id/uploads/thumbnails/course_thumbnails/course_thumbnail_default_63.jpg', + title: courses.title, + instructorName: courses.instructorName, + specificRating: double.parse(courses + .rating[0]!.avgRating != + null + ? '${courses.rating[0]!.avgRating}' + : '5.0') + .toString(), + rating: courses.rating[0]!.avgRating != null + ? '${courses.rating[0]!.avgRating}' + : '5.0', + numberOfRatings: + courses.rating[0]!.totalReview ?? '0', + isTopCourse: '0', + price: (courses.discountPrice == '0') + ? 'Gratis' + : numberFormat(displayedPrice), + realPrice: (courses.price == '0') + ? '' + : numberFormat(courses.price), + press: () async { + Navigator.of(context, rootNavigator: true) + .push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: courses.idCourse, + ), + ), + ); + }, + ), + ); + }, + ), + ], + ), + ), + if (widget.homeCategories == null) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topLeft, + widthFactor: 1, + heightFactor: 1, + child: Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(87)), + child: Categories( + selectedCategoryId: widget.categoryId, + key: UniqueKey(), + scrollController: _scrollController, + ), + ), + ), + ), + ], + ); + } else if (state.state == ResultState.NoData) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppBarHeader(), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Text( + '${widget.name.replaceAll('&', '&')}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(25), + letterSpacing: -0.5, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.start, + ), + ), + Categories( + selectedCategoryId: widget.categoryId, + key: UniqueKey(), + scrollController: _scrollController, + ), + SizedBox(height: getProportionateScreenHeight(200)), + Center( + child: Text( + "Oops! Tidak ada kursus yang tersedia dalam kategori ini.", + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + color: Colors.grey, + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(60)), + ], + ); + } else if (state.state == ResultState.Error) { + return Center(child: Text("No internet connections.")); + } else { + return Center(child: Text('')); + } + }), + ), + ), + ), + ), + ); + } + + void _collapseSubcategories() { + _scrollController.animateTo( + 0, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + } +} diff --git a/lib/screens/home/components/body_comp/course_by_subcategory.dart b/lib/screens/home/components/body_comp/course_by_subcategory.dart new file mode 100644 index 0000000..d80ae95 --- /dev/null +++ b/lib/screens/home/components/body_comp/course_by_subcategory.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/course_by_category_provider.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar_filter.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/services/course_by_category_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; + +class CourseBySubcategory extends StatefulWidget { + CourseBySubcategory({ + Key? key, + required this.name, + required this.categoryId, + required this.subId, + }) : super(key: key); + + final String name; + final String categoryId; + final String subId; + + @override + State<CourseBySubcategory> createState() => _CourseBySubcategoryState(); +} + +class _CourseBySubcategoryState extends State<CourseBySubcategory> { + bool _isExpanded = false; + bool _buttonPressed = false; + final ScrollController _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + controller: _scrollController, + child: ChangeNotifierProvider<CourseByCategoryProvider>( + create: (context) => CourseByCategoryProvider( + courseByCategoryService: CourseByCategoryService(), + id: widget.categoryId, + subId: widget.subId, + fetchBySubcategory: true, + ), + child: Container( + child: Consumer<CourseByCategoryProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + return GestureDetector( + onTap: () { + setState(() { + _isExpanded = !_isExpanded; + _buttonPressed = !_buttonPressed; + }); + }, + child: Stack( + children: [ + GestureDetector( + child: Container( + color: Colors.transparent, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppBarFilter(), + SizedBox(height: getProportionateScreenHeight(10)), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Text( + '${widget.name}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(20), + letterSpacing: -0.5, + fontWeight: bold, + ), + textAlign: TextAlign.start, + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Text( + "Semua Kursus ${widget.name}", + style: + thirdTextStyle.copyWith(fontWeight: bold), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + GridView.builder( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(22), + left: getProportionateScreenWidth(7), + ), + physics: ScrollPhysics(), + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.8 / 4, + crossAxisSpacing: 16, + mainAxisSpacing: 12, + ), + itemCount: state.result.length, + itemBuilder: (context, index) { + var courses = state.result[index]; + int price = int.tryParse( + courses.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse(courses + .discountPrice + .replaceAll('.', '')) ?? + 0; + + int calculatedPrice = + (courses.discountPrice != '0') + ? price - discountPrice + : price; + String displayedPrice = (calculatedPrice == 0) + ? courses.price + : calculatedPrice.toString(); + + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(17)), + child: ProductCard( + totalDiscount: courses.totalDiscount ?? 0, + students: courses.students ?? '0', + id: courses.idCourse, + thumbnail: courses.thumbnail ?? + 'https://vocasia.id/uploads/thumbnails/course_thumbnails/course_thumbnail_default_63.jpg', + title: courses.title, + instructorName: courses.instructorName, + specificRating: double.parse(courses + .rating[0]!.avgRating != + null + ? '${courses.rating[0]!.avgRating}' + : '5.0') + .toString(), + rating: courses.rating[0]!.avgRating != null + ? '${courses.rating[0]!.avgRating}' + : '5.0', + numberOfRatings: + courses.rating[0]!.totalReview ?? '0', + isTopCourse: '0', + price: (courses.discountPrice == '0') + ? 'Gratis' + : numberFormat(displayedPrice), + realPrice: (courses.price == '0') + ? '' + : numberFormat(courses.price), + press: () async { + Navigator.of(context, rootNavigator: true) + .push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: courses.idCourse, + ), + ), + ); + }, + ), + ); + }, + ), + ], + ), + ], + ), + ); + } else if (state.state == ResultState.NoData) { + return Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AppBarHeader(), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Text( + '${widget.name.replaceAll('&', '&')}', + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(25), + letterSpacing: -0.5, + ), + textAlign: TextAlign.start, + ), + ), + ], + ), + Center( + child: Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(300)), + child: Text( + "Sub kategori yang dipilih tidak memiliki kursus", + style: TextStyle( + fontSize: getProportionateScreenWidth(10)), + ), + ), + ), + ], + ); + } else if (state.state == ResultState.Error) { + return Center(child: Text("Terjadi kesalahan.")); + } else { + return Center(child: Text('')); + } + }), + ), + ), + ), + ), + ); + } + + void _collapseSubcategories() { + _scrollController.animateTo( + 0, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + } +} diff --git a/lib/screens/home/components/body_comp/course_terkait.dart b/lib/screens/home/components/body_comp/course_terkait.dart new file mode 100644 index 0000000..011b442 --- /dev/null +++ b/lib/screens/home/components/body_comp/course_terkait.dart @@ -0,0 +1,173 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/course_by_category_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/services/course_by_category_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; + +class CourseTerkait extends StatefulWidget { + CourseTerkait({ + Key? key, + required this.name, + required this.categoryId, + required this.subId, + this.homeCategories, + }) : super(key: key); + + final String name; + final String categoryId; + final String subId; + final bool? homeCategories; + + @override + State<CourseTerkait> createState() => _CourseTerkaitState(); +} + +class _CourseTerkaitState extends State<CourseTerkait> { + final ScrollController _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + controller: _scrollController, + child: ChangeNotifierProvider<CourseByCategoryProvider>( + create: (context) => CourseByCategoryProvider( + courseByCategoryService: CourseByCategoryService(), + id: widget.categoryId, + subId: widget.subId, + fetchBySubcategory: false, + ), + child: Container( + child: Consumer<CourseByCategoryProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + return Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(13)), + Text( + ' Kursus Terkait', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(15), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(20)), + GridView.builder( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(21), + left: getProportionateScreenWidth(7), + bottom: getProportionateScreenHeight(18), + ), + physics: BouncingScrollPhysics(), + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.8 / 4, + crossAxisSpacing: 16, + mainAxisSpacing: 14, + ), + itemCount: state.result.length, + itemBuilder: (context, index) { + var courses = state.result[index]; + int price = int.tryParse( + courses.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse(courses + .discountPrice + .replaceAll('.', '')) ?? + 0; + + int calculatedPrice = + (courses.discountPrice != '0') + ? price - discountPrice + : price; + String displayedPrice = (calculatedPrice == 0) + ? courses.price + : calculatedPrice.toString(); + + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(13)), + child: ProductCard( + totalDiscount: courses.totalDiscount ?? 0, + students: courses.students ?? '0', + id: courses.idCourse, + thumbnail: courses.thumbnail ?? + 'https://vocasia.id/uploads/thumbnails/course_thumbnails/course_thumbnail_default_63.jpg', + title: courses.title, + instructorName: courses.instructorName, + specificRating: double.parse(courses + .rating[0]!.avgRating != + null + ? '${courses.rating[0]!.avgRating}' + : '5.0') + .toString(), + rating: courses.rating[0]!.avgRating != null + ? '${courses.rating[0]!.avgRating}' + : '5.0', + numberOfRatings: + courses.rating[0]!.totalReview ?? '0', + isTopCourse: '0', + price: (courses.discountPrice == '0') + ? 'Gratis' + : numberFormat(displayedPrice), + realPrice: (courses.price == '0') + ? '' + : numberFormat(courses.price), + press: () async { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + DetailCourseScreen( + idcourse: courses.idCourse, + ), + ), + ); + }, + ), + ); + }, + ), + SizedBox(height: getProportionateScreenHeight(50)), + ], + ), + ], + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text("Tidak ada kursus terkait")); + } else if (state.state == ResultState.Error) { + return Center(child: Text("No internet connections.")); + } else { + return Center(child: Text('')); + } + }, + ), + ), + ), + ), + ); + } +} diff --git a/lib/screens/home/components/body_comp/home_categories.dart b/lib/screens/home/components/body_comp/home_categories.dart new file mode 100644 index 0000000..856768c --- /dev/null +++ b/lib/screens/home/components/body_comp/home_categories.dart @@ -0,0 +1,256 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/providers/categories_provider.dart'; +import 'package:initial_folder/screens/home/components/body_comp/course_by_category.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; + +class HomeCategories extends StatelessWidget { + const HomeCategories({super.key}); + + @override + Widget build(BuildContext context) { + CategoriesProvider categoriesProvider = + Provider.of<CategoriesProvider>(context); + return Padding( + padding: EdgeInsets.only(left: getProportionateScreenWidth(17)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(15)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Kategori', + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + GestureDetector( + onTap: () => + Navigator.pushNamed(context, '/lihatSemuaKategori'), + child: Text( + 'Lihat Semua', + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(11), + ), + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(18)), + Stack( + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(22), + right: getProportionateScreenWidth(14), + ), + child: categoriesProvider.state == ResultState.Loading + ? GridView.count( + crossAxisCount: 2, + childAspectRatio: 5 / 2, + mainAxisSpacing: 13, + crossAxisSpacing: 30, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: List.generate(4, (index) { + return Shimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: SizedBox( + width: getProportionateScreenWidth(160), + child: Container( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(5), + horizontal: getProportionateScreenWidth(10), + ), + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey, + ), + borderRadius: BorderRadius.circular(15), + ), + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(18)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + height: + getProportionateScreenHeight(10), + ), + SizedBox( + height: + getProportionateScreenHeight(7)), + Container( + color: Colors.white, + height: + getProportionateScreenHeight(10), + ), + ], + ), + ), + ), + ), + ); + }), + ) + : GridView.count( + crossAxisCount: 2, + childAspectRatio: 5 / 2, + mainAxisSpacing: 13, + crossAxisSpacing: 30, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: List.generate(4, (index) { + return GestureDetector( + onTap: () { + Navigator.of(context).push( + CustomNavigator( + child: CourseByCategory( + name: categoriesProvider + .result[index].nameCategory! ?? + '', + categoryId: + categoriesProvider.result[index].id!, + subId: "", + homeCategories: true, + ), + ), + ); + }, + child: SizedBox( + width: getProportionateScreenWidth(160), + child: Container( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(5), + horizontal: getProportionateScreenWidth(10), + ), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : Colors.grey, + ), + borderRadius: BorderRadius.circular(15), + ), + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(18)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + categoriesProvider + .result[index].nameCategory!.replaceAll('&', '&'), + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(10), + letterSpacing: 1, + fontWeight: bold, + ), + ), + ], + ), + ), + ), + ), + ); + }), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topLeft, + widthFactor: 0.12, + heightFactor: 0.44, + child: SvgPicture.asset( + "assets/icons/category1.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topLeft, + widthFactor: 0.12, + heightFactor: 1.53, + child: SvgPicture.asset( + "assets/icons/category3.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topCenter, + widthFactor: 0.6, + heightFactor: 0.4, + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(37), + top: getProportionateScreenHeight(7), + ), + child: SvgPicture.asset( + "assets/icons/category2.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomCenter, + widthFactor: 0.53, + heightFactor: 0.39, + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(37), + bottom: getProportionateScreenHeight(7), + ), + child: SvgPicture.asset( + "assets/icons/category4.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ), + ); + } +} diff --git a/lib/screens/home/components/body_comp/latest_course.dart b/lib/screens/home/components/body_comp/latest_course.dart new file mode 100644 index 0000000..d2decd5 --- /dev/null +++ b/lib/screens/home/components/body_comp/latest_course.dart @@ -0,0 +1,485 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/latest_course_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card_new.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../../../size_config.dart'; +import '../../../../theme.dart'; + +class LatestCourse extends StatelessWidget { + const LatestCourse({Key? key, this.text}) : super(key: key); + + final String? text; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + SizedBox(height: getProportionateScreenHeight(20)), + Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Text( + text.toString(), + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + ]), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Consumer<LatestCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else if (state.state == ResultState.HasData) { + return text != "New Release" + ? Container( + alignment: Alignment.centerLeft, + margin: EdgeInsets.only( + left: getProportionateScreenWidth(10)), + height: getProportionateScreenHeight(240), + child: ListView.builder( + scrollDirection: Axis.horizontal, + physics: const ScrollPhysics(), + shrinkWrap: true, + itemCount: state.result.length, + itemBuilder: (context, index) { + var latestCourse = state.result[index]; + int price = int.tryParse( + latestCourse.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse(latestCourse + .discountPrice + .replaceAll('.', '')) ?? + 0; + + int calculatedPrice = + (latestCourse.discountPrice != '0') + ? price - discountPrice + : price; + + String displayedPrice = (calculatedPrice == 0) + ? latestCourse.price + : calculatedPrice.toString(); + // var finalRating = double.parse( + // (topCourse.specificRating![0] / 20).toStringAsFixed(2)); + return Container( + margin: EdgeInsets.only( + top: getProportionateScreenHeight(10), + bottom: getProportionateScreenHeight(10), + ), + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(18), + bottom: getProportionateScreenWidth(5), + ), + child: ProductCardNew( + totalDiscount: latestCourse.totalDiscount ?? 0, + students: latestCourse.students ?? '0', + pad: 12, + // padRight: 12, + id: latestCourse.idCourse, + thumbnail: latestCourse.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: latestCourse.title, + instructorName: latestCourse.instructorName, + specificRating: + (latestCourse.rating.isNotEmpty && + latestCourse.rating[0]?.avgRating != + null) + ? latestCourse.rating[0]!.avgRating + .toString() + : '0', + rating: + latestCourse.rating[0]!.avgRating != null + ? '${latestCourse.rating[0]!.avgRating}' + : '5.0', + numberOfRatings: (latestCourse + .rating.isNotEmpty && + latestCourse.rating[0]?.totalReview != + null) + ? latestCourse.rating[0]!.totalReview! + : '0', + isTopCourse: latestCourse.topCourse ?? '', + price: (latestCourse.price == '0') + ? 'Gratis' + : (latestCourse.promoPrice != '0') + ? numberFormat(latestCourse.promoPrice) + : numberFormat(displayedPrice), + realPrice: (latestCourse.price == '0') + ? '' + : numberFormat(latestCourse.price), + press: () { + print(latestCourse.idCourse); + Navigator.of(context, rootNavigator: true) + .push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: latestCourse.idCourse, + isPromo: latestCourse.promoPrice != '0' + ? true + : null, + ), + ), + ); + }, + ), + ), + ); + }, + ), + ) + : GridView.builder( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(22), + left: getProportionateScreenWidth(7), + top: getProportionateScreenHeight(10), + ), + physics: ScrollPhysics(), + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.55 / 4, + crossAxisSpacing: 16, + mainAxisSpacing: 12, + ), + itemCount: state.result.length, + itemBuilder: (context, index) { + var latestCourse = state.result[index]; + int price = int.tryParse( + latestCourse.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse(latestCourse + .discountPrice + .replaceAll('.', '')) ?? + 0; + + int calculatedPrice = + (latestCourse.discountPrice != '0') + ? price - discountPrice + : price; + + String displayedPrice = (calculatedPrice == 0) + ? latestCourse.price + : calculatedPrice.toString(); + + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(17)), + child: ProductCardNew( + totalDiscount: latestCourse.totalDiscount ?? 0, + students: latestCourse.students ?? '0', + id: latestCourse.idCourse, + thumbnail: latestCourse.thumbnail ?? + 'https://vocasia.id/uploads/thumbnails/course_thumbnails/course_thumbnail_default_63.jpg', + title: latestCourse.title, + instructorName: latestCourse.instructorName, + specificRating: double.parse( + latestCourse.rating[0]!.avgRating != null + ? '${latestCourse.rating[0]!.avgRating}' + : '5.0') + .toString(), + rating: latestCourse.rating[0]!.avgRating != null + ? '${latestCourse.rating[0]!.avgRating}' + : '5.0', + numberOfRatings: + latestCourse.rating[0]!.totalReview ?? '0', + isTopCourse: '0', + price: (latestCourse.discountPrice == '0') + ? 'Gratis' + : numberFormat(displayedPrice), + realPrice: (latestCourse.price == '0') + ? '' + : numberFormat(latestCourse.price), + press: () async { + // await Hive.openBox<Wishlist>("wishlist"); + // await Hive.openBox('carts'); + + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: latestCourse.idCourse, + isPromo: latestCourse.promoPrice != '0' + ? true + : null, + ), + ), + ); + }, + ), + ); + }, + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else { + return Center(child: Text('')); + } + }, + ) + ], + ); + } + + // @override + // Widget build(BuildContext context) { + // return Column( + // children: [ + // Padding( + // padding: + // EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + // child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + // Text('Kursus Terbaru', + // textAlign: TextAlign.left, + // style: secondaryTextStyle.copyWith( + // letterSpacing: 1, + // color: tenthColor, + // fontSize: getProportionateScreenWidth(14), + // fontWeight: semiBold)), + // ]), + // ), + // SizedBox(height: 20), + // Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Icon(Icons.construction_outlined), + // Text('Under Construction'), + // ], + // ), + // ], + // ); + // } +} diff --git a/lib/screens/home/components/body_comp/lihat_semua_kategori.dart b/lib/screens/home/components/body_comp/lihat_semua_kategori.dart new file mode 100644 index 0000000..9ea1c17 --- /dev/null +++ b/lib/screens/home/components/body_comp/lihat_semua_kategori.dart @@ -0,0 +1,273 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/providers/categories_provider.dart' + as categoriesProv; +import 'package:initial_folder/providers/others_course_provider.dart'; +import 'package:initial_folder/screens/home/components/body_comp/course_by_category.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import '../../../../size_config.dart'; + +class LihatSemuaKategori extends StatefulWidget { + const LihatSemuaKategori({Key? key}) : super(key: key); + + @override + State<LihatSemuaKategori> createState() => _LihatSemuaKategoriState(); +} + +class _LihatSemuaKategoriState extends State<LihatSemuaKategori> { + @override + Widget build(BuildContext context) { + categoriesProv.CategoriesProvider categoriesProvider = + Provider.of<categoriesProv.CategoriesProvider>(context); + + return SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + "Kategori", + style: thirdTextStyle.copyWith( + fontWeight: bold, fontSize: getProportionateScreenWidth(16)), + ), + ), + body: Padding( + padding: EdgeInsets.only(left: getProportionateScreenWidth(15)), + child: Stack( + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(20), + right: getProportionateScreenWidth(14), + top: getProportionateScreenHeight(10), + ), + child: categoriesProvider.state == ResultState.Loading + ? Center(child: CircularProgressIndicator()) + : GridView.count( + crossAxisCount: 2, + childAspectRatio: 4.3 / 2, + mainAxisSpacing: 13, + crossAxisSpacing: 30, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + children: List.generate( + categoriesProvider.result.length, (index) { + return GestureDetector( + onTap: () { + Navigator.of(context).push( + CustomNavigator( + child: CourseByCategory( + name: categoriesProvider + .result[index].nameCategory! ?? + '', + categoryId: + categoriesProvider.result[index].id!, + subId: "", + homeCategories: true, + ), + ), + ); + }, + child: SizedBox( + width: getProportionateScreenWidth(160), + child: Container( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(5), + horizontal: getProportionateScreenWidth(10), + ), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).brightness == + Brightness.dark + ? baruTextutih + : Colors.grey, + ), + borderRadius: BorderRadius.circular(15), + ), + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(18)), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + categoriesProvider + .result[index].nameCategory!.replaceAll('&', '&'), + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(10), + fontWeight: bold, + ), + ), + ], + ), + ), + ), + ), + ); + }), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topLeft, + widthFactor: 0.12, + heightFactor: 0.2, + child: Padding( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(9)), + child: SvgPicture.asset( + "assets/icons/category1.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topLeft, + widthFactor: 0.12, + heightFactor: 0.59, + child: Padding( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(9)), + child: SvgPicture.asset( + "assets/icons/category3.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topRight, + widthFactor: 0.51, + heightFactor: 0.2, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(133), + top: getProportionateScreenHeight(10), + ), + child: SvgPicture.asset( + "assets/icons/category2.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.topRight, + widthFactor: 0.51, + heightFactor: 0.6, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(133), + top: getProportionateScreenHeight(10), + ), + child: SvgPicture.asset( + "assets/icons/category4.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.centerLeft, + widthFactor: 0.12, + heightFactor: 0.7, + child: Padding( + padding: + EdgeInsets.only(top: getProportionateScreenHeight(9)), + child: SvgPicture.asset( + "assets/icons/category5.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomLeft, + widthFactor: 0.12, + heightFactor: 0.57, + child: SvgPicture.asset( + "assets/icons/category7.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.centerRight, + widthFactor: 0.51, + heightFactor: 0.75, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(133), + top: getProportionateScreenHeight(10), + ), + child: SvgPicture.asset( + "assets/icons/category6.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomRight, + widthFactor: 0.51, + heightFactor: 0.57, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(133)), + child: SvgPicture.asset( + "assets/icons/category8.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ), + if (categoriesProvider.state != ResultState.Loading) + Positioned.fill( + child: FractionallySizedBox( + alignment: Alignment.bottomLeft, + widthFactor: 0.12, + heightFactor: 0.17, + child: SvgPicture.asset( + "assets/icons/category9.svg", + width: getProportionateScreenWidth(35), + height: getProportionateScreenHeight(35), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/home/components/body_comp/lihat_semua_kursus_page.dart b/lib/screens/home/components/body_comp/lihat_semua_kursus_page.dart new file mode 100644 index 0000000..047dcea --- /dev/null +++ b/lib/screens/home/components/body_comp/lihat_semua_kursus_page.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/others_course_provider.dart'; +import 'package:initial_folder/screens/detail_course/components/app_bar.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'others_course.dart'; +import 'product_card/product_card.dart'; +import '../../../../size_config.dart'; +import 'section_title.dart'; +import 'package:intl/intl.dart'; + +class LihatSemuaKursus extends StatefulWidget { + static String routeName = "/lihatKursus"; + + const LihatSemuaKursus({Key? key}) : super(key: key); + + @override + State<LihatSemuaKursus> createState() => _LihatSemuaKursusState(); +} + +class _LihatSemuaKursusState extends State<LihatSemuaKursus> { + ScrollController _controller = ScrollController(); + + @override + void initState() { + _controller.addListener(() { + onScroll(); + }); + } + + void onScroll() async { + double maxScroll = _controller.position.maxScrollExtent; + double currentScroll = _controller.position.pixels; + if (maxScroll == currentScroll) { + await Provider.of<OthersCourseProvider>(context, listen: false) + .getOthersCourses(); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (_controller.hasClients) { + if (_controller.position.pixels != _controller.position.maxScrollExtent) { + _controller.animateTo(0, + duration: Duration(milliseconds: 900), curve: Curves.linear); + } + } + return SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + "Kursus Lainnya", + style: thirdTextStyle.copyWith( + fontWeight: bold, fontSize: getProportionateScreenWidth(16)), + ), + ), + body: Column( + children: [ + Consumer<OthersCourseProvider>(builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + return Expanded( + child: ListView(controller: _controller, children: [ + OthersCourse( + key: PageStorageKey<String>('lihatKursus'), + showTitle: true, + ), + Provider.of<OthersCourseProvider>(context).loading + ? Center( + child: SizedBox( + height: 30, + width: 30, + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ), + ) + : SizedBox(height: 30), + SizedBox(height: 25), + ]), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Center( + child: Column( + children: [ + Text( + 'Terjadi Kesalahan Coba Lagi', + style: thirdTextStyle, + ), + ], + )); + } else { + return Center(child: Text('')); + } + }) + ], + ), + ), + ); + } +} + +// courseProvider.course +// .map( +// (product) => ProductCard( +// product: product, +// ), +// ) +// .toList(), diff --git a/lib/screens/home/components/body_comp/list_of_categories.dart b/lib/screens/home/components/body_comp/list_of_categories.dart new file mode 100644 index 0000000..bffb10b --- /dev/null +++ b/lib/screens/home/components/body_comp/list_of_categories.dart @@ -0,0 +1,239 @@ +import 'dart:ui'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/subcategories_model.dart'; +import 'package:initial_folder/providers/categories_provider.dart'; +import 'package:initial_folder/screens/home/components/body_comp/course_by_subcategory.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; + +class Categories extends StatefulWidget { + const Categories({ + Key? key, + this.scrollable = false, + required this.selectedCategoryId, + required this.scrollController, + }) : super(key: key); + final bool scrollable; + final String selectedCategoryId; + final ScrollController scrollController; + + @override + State<Categories> createState() => _CategoriesState(); +} + +class _CategoriesState extends State<Categories> { + bool _isExpanded = false; + bool _buttonPressed = false; + final GlobalKey<_CategoriesState> _categoriesKey = + GlobalKey<_CategoriesState>(); + + @override + void initState() { + super.initState(); + widget.scrollController.addListener(_scrollListener); + } + + @override + void dispose() { + widget.scrollController.removeListener(_scrollListener); + super.dispose(); + } + + void _scrollListener() { + if (_isExpanded) { + setState(() { + _isExpanded = false; + _buttonPressed = false; + }); + } + } + + void toggleExpansion(bool expanded) { + setState(() { + _isExpanded = expanded; + _buttonPressed = expanded; + }); + + if (!expanded) { + widget.scrollController.animateTo( + 0, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + ); + } + } + + @override + Widget build(BuildContext context) { + return Container( + child: Consumer<CategoriesProvider>(builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.HasData) { + final List<SubCategoryModel> filteredResult = state.result + .where((category) => category.id == widget.selectedCategoryId) + .map((category) => category.subCategories!) + .expand((element) => element) + .toList(); + final bool showExpandIcon = filteredResult.length > 3; + return Container( + margin: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(10)), + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.grey.withOpacity(0.6) + : Colors.black.withOpacity(0.2), + spreadRadius: -10, + blurRadius: 12, + offset: Offset(0, 9), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ...filteredResult + .take(_buttonPressed ? 2 : 2) + .map<Widget>((subcategory) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(17)), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + CustomNavigator( + child: CourseBySubcategory( + categoryId: widget.selectedCategoryId, + subId: subcategory.subId, + name: subcategory.nameSubCategory, + ), + ), + ); + }, + child: Text( + subcategory.nameSubCategory.isNotEmpty + ? subcategory.nameSubCategory.replaceAll('&', '&') + : 'No subcategories available', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11.5), + fontWeight: FontWeight.w600, + ), + ), + ), + ); + }).toList(), + if (showExpandIcon) + IconButton( + icon: Transform.rotate( + angle: _isExpanded ? pi / 2 : 0, + child: Icon( + Icons.arrow_forward_ios, + size: getProportionateScreenWidth(16), + ), + ), + onPressed: () { + setState(() { + _isExpanded = !_isExpanded; + _buttonPressed = !_buttonPressed; + }); + }, + ), + ], + ), + if (_isExpanded) + Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(8)), + child: Container( + color: Theme.of(context).colorScheme.background, + height: 160, + child: ListView( + shrinkWrap: true, + physics: AlwaysScrollableScrollPhysics(), + children: filteredResult + .skip(2) + .map<Widget>((subcategory) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: + getProportionateScreenWidth(17)), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + CustomNavigator( + child: CourseBySubcategory( + categoryId: + widget.selectedCategoryId, + subId: subcategory.subId, + name: subcategory.nameSubCategory, + ), + ), + ); + }, + child: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight(10)), + child: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight( + 9)), + child: Text( + subcategory.nameSubCategory.isNotEmpty + ? subcategory.nameSubCategory.replaceAll('&', '&') + : 'No subcategories available', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 11.5), + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ), + ); + }).toList(), + ), + ), + ), + ], + ), + ), + ], + ), + key: _categoriesKey, + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text("No subcategories available")); + } else if (state.state == ResultState.Error) { + return Center(child: Text("No internet connections.")); + } else { + print(state.result.length); + return Center(child: Text('')); + } + }), + ); + } +} diff --git a/lib/screens/home/components/body_comp/others_course.dart b/lib/screens/home/components/body_comp/others_course.dart new file mode 100644 index 0000000..ace27a1 --- /dev/null +++ b/lib/screens/home/components/body_comp/others_course.dart @@ -0,0 +1,368 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/others_course_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/lihat_semua_kursus_page.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import 'product_card/product_card.dart'; +import '../../../../size_config.dart'; +import 'section_title.dart'; + +class OthersCourse extends StatelessWidget { + final bool? showTitle; + const OthersCourse({Key? key, this.showTitle}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: (showTitle == null) + ? SectionTitle( + title: "Kursus Lainnya", + press: () { + Navigator.push( + context, + CustomNavigator( + child: LihatSemuaKursus(), + ), + ); + }, + ) + : SizedBox()), + SizedBox(height: getProportionateScreenHeight(30)), + Consumer<OthersCourseProvider>(builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else if (state.state == ResultState.HasData) { + // state.result.shuffle(); + return GridView.builder( + // controller: _controller, + + padding: EdgeInsets.only( + right: getProportionateScreenWidth(23), + left: getProportionateScreenWidth(8), + ), + physics: ScrollPhysics(), + shrinkWrap: true, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.6 / 4, + crossAxisSpacing: 15, + mainAxisSpacing: 10, + ), + itemCount: state.result.length, + itemBuilder: (context, index) { + var othersCourse = state.result[index]; + int price = + int.tryParse(othersCourse.price.replaceAll('.', '')) ?? 0; + int discountPrice = int.tryParse( + othersCourse.discountPrice.replaceAll('.', '')) ?? + 0; + + int calculatedPrice = (othersCourse.discountPrice != '0') + ? price - discountPrice + : price; + + String displayedPrice = (calculatedPrice == 0) + ? othersCourse.price + : calculatedPrice.toString(); + // var finalRating = double.parse( + // (othersCourse.specificRating![0] / 20).toStringAsFixed(2)); + return Padding( + padding: + EdgeInsets.only(bottom: getProportionateScreenHeight(8)), + child: Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(12)), + child: ProductCard( + totalDiscount: othersCourse.totalDiscount ?? 0, + students: othersCourse.students ?? '0', + id: othersCourse.idCourse, + thumbnail: othersCourse.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: othersCourse.title, + instructorName: othersCourse.instructorName, + specificRating: (othersCourse.rating.isNotEmpty && + othersCourse.rating[0]?.avgRating != null) + ? othersCourse.rating[0]!.avgRating.toString() + : '0', + rating: (othersCourse.rating.isNotEmpty && + othersCourse.rating[0]?.avgRating != null) + ? othersCourse.rating[0]!.avgRating.toString() + : '5.0', + numberOfRatings: (othersCourse.rating.isNotEmpty && + othersCourse.rating[0]?.totalReview != null) + ? othersCourse.rating[0]!.totalReview! + : '0', + isTopCourse: othersCourse.topCourse ?? '', + price: (othersCourse.price == '0') + ? 'Gratis' + : (othersCourse.promoPrice != '0') + ? numberFormat(othersCourse.promoPrice) + : numberFormat(displayedPrice), + realPrice: (othersCourse.price == '0') + ? '' + : numberFormat(othersCourse.price), + press: () { + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: othersCourse.idCourse, + isPromo: + othersCourse.promoPrice != "0" ? true : null, + ), + ), + ); + }, + ), + ), + ); + }, + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else { + return Center(child: Text('')); + } + }) + ], + ); + } +} + +// courseProvider.course +// .map( +// (product) => ProductCard( +// product: product, +// ), +// ) +// .toList(), diff --git a/lib/screens/home/components/body_comp/populer_course.dart b/lib/screens/home/components/body_comp/populer_course.dart new file mode 100644 index 0000000..5ac358c --- /dev/null +++ b/lib/screens/home/components/body_comp/populer_course.dart @@ -0,0 +1,361 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/top_course_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../../../size_config.dart'; +import '../../../../theme.dart'; + +class PopulerCourse extends StatelessWidget { + const PopulerCourse({Key? key, this.text}) : super(key: key); + final String? text; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + text.toString(), + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Consumer<TopCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else if (state.state == ResultState.HasData) { + return Container( + alignment: Alignment.centerLeft, + margin: EdgeInsets.only(left: getProportionateScreenWidth(10)), + height: getProportionateScreenHeight(220), + child: ListView.builder( + scrollDirection: Axis.horizontal, + physics: ScrollPhysics(), + shrinkWrap: true, + itemCount: state.result.length, + itemBuilder: (context, index) { + var topCourse = state.result[index]; + // var finalRating = double.parse( + // (topCourse.specificRating![0] / 20).toStringAsFixed(2)); + int price = + int.tryParse(topCourse.price.replaceAll('.', '')) ?? 0; + int discountPrice = int.tryParse( + topCourse.discountPrice.replaceAll('.', '')) ?? + 0; + + int calculatedPrice = (topCourse.discountPrice != '0') + ? price - discountPrice + : price; + + String displayedPrice = (calculatedPrice == 0) + ? topCourse.price + : calculatedPrice.toString(); + return Container( + margin: EdgeInsets.only( + top: getProportionateScreenHeight(10), + ), + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(18), + bottom: getProportionateScreenWidth(18), + ), + child: ProductCard( + totalDiscount: topCourse.totalDiscount ?? 0, + students: topCourse.students ?? '0', + pad: 12, + id: topCourse.idCourse, + thumbnail: topCourse.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: topCourse.title, + instructorName: topCourse.instructorName, + specificRating: (topCourse.rating.isNotEmpty && + topCourse.rating[0]?.avgRating != null) + ? topCourse.rating[0]!.avgRating.toString() + : '0', + rating: (topCourse.rating.isNotEmpty && + topCourse.rating[0]?.avgRating != null) + ? topCourse.rating[0]!.avgRating.toString() + : '5.0', + numberOfRatings: (topCourse.rating.isNotEmpty && + topCourse.rating[0]?.totalReview != null) + ? topCourse.rating[0]!.totalReview! + : '0', + isTopCourse: topCourse.topCourse!, + price: (topCourse.price == '0') + ? 'Gratis' + : (topCourse.promoPrice != '0') + ? numberFormat(topCourse.promoPrice) + : numberFormat(displayedPrice), + realPrice: (topCourse.price == '0') + ? '' + : numberFormat(topCourse.price), + press: () { + print(topCourse.idCourse); + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: topCourse.idCourse, + ), + ), + ); + }, + ), + ), + ); + }, + ), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else { + return Center(child: Text('')); + } + }, + ) + ], + ); + } +} diff --git a/lib/screens/home/components/body_comp/populer_course_big.dart b/lib/screens/home/components/body_comp/populer_course_big.dart new file mode 100644 index 0000000..e568df0 --- /dev/null +++ b/lib/screens/home/components/body_comp/populer_course_big.dart @@ -0,0 +1,243 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/top_course_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card_big.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../../../size_config.dart'; +import '../../../../theme.dart'; + +class PopulerCourseBig extends StatelessWidget { + const PopulerCourseBig({Key? key, this.text}) : super(key: key); + final String? text; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + text!, + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Consumer<TopCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else if (state.state == ResultState.HasData) { + return Container( + alignment: Alignment.centerLeft, + margin: EdgeInsets.only(left: getProportionateScreenWidth(4)), + height: getProportionateScreenHeight(270), + child: ListView.builder( + scrollDirection: Axis.horizontal, + physics: ScrollPhysics(), + shrinkWrap: true, + itemCount: state.result.length, + itemBuilder: (context, index) { + var topCourse = state.result[index]; + // var finalRating = double.parse( + // (topCourse.specificRating![0] / 20).toStringAsFixed(2)); + return Container( + margin: EdgeInsets.only( + top: getProportionateScreenHeight(10), + ), + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(13), + bottom: getProportionateScreenWidth(18), + ), + child: ProductCardBig( + totalDiscount: topCourse.totalDiscount ?? 0, + students: topCourse.students ?? '0', + pad: 12, + id: topCourse.idCourse, + thumbnail: topCourse.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: topCourse.title, + instructorName: topCourse.instructorName, + specificRating: (topCourse.rating.isNotEmpty && + topCourse.rating[0]?.avgRating != null) + ? topCourse.rating[0]!.avgRating.toString() + : '0', + rating: (topCourse.rating.isNotEmpty && + topCourse.rating[0]?.avgRating != null) + ? topCourse.rating[0]!.avgRating.toString() + : '5.0', + numberOfRatings: (topCourse.rating.isNotEmpty && + topCourse.rating[0]?.totalReview != null) + ? topCourse.rating[0]!.totalReview! + : '0', + isTopCourse: topCourse.topCourse!, + price: (topCourse.discountPrice == '0') + ? 'Gratis' + : numberFormat(topCourse.discountPrice), + realPrice: (topCourse.price == '0') + ? '' + : numberFormat(topCourse.price), + press: () { + print(topCourse.idCourse); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailCourseScreen( + idcourse: topCourse.idCourse, + ), + ), + ); + }, + ), + ), + ); + }, + ), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Center( + child: Column( + children: [ + Text('Terjadi Kesalahan Coba Lagi'), + ], + ), + ); + } else { + return Center(child: Text('')); + } + }, + ) + ], + ); + } +} diff --git a/lib/screens/home/components/body_comp/product_card/product_card.dart b/lib/screens/home/components/body_comp/product_card/product_card.dart new file mode 100644 index 0000000..0cf1e4d --- /dev/null +++ b/lib/screens/home/components/body_comp/product_card/product_card.dart @@ -0,0 +1,328 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../../../../theme.dart'; +import '../../../../../size_config.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +class ProductCard extends StatelessWidget { + const ProductCard({ + Key? key, + this.width = 120, + this.pad = 16, + this.padRight = 0, + this.isHome, + required this.thumbnail, + required this.id, + required this.isTopCourse, + required this.title, + required this.instructorName, + required this.rating, + required this.specificRating, + required this.numberOfRatings, + required this.price, + required this.realPrice, + required this.press, + required this.students, + required this.totalDiscount, + }) : super(key: key); + + final double width; + final double pad, padRight; + final String thumbnail, + title, + instructorName, + price, + realPrice, + rating, + specificRating, + numberOfRatings, + id, + isTopCourse; + final String students; + final VoidCallback press; + final int totalDiscount; + final bool? isHome; + // final VoidCallback? whislistPress; + // final Widget? iconWishlist; + + @override + Widget build(BuildContext context) { + //CourseProvider courseProvider = Provider.of<CourseProvider>(context); + // final formatCurrency = + // new NumberFormat.simpleCurrency(decimalDigits: 0, locale: 'id_ID'); + bool isRatingValid = int.tryParse(rating) != null; + bool isStudentsValid = int.tryParse(students) != null; + final themeProvider = Provider.of<ThemeProvider>(context); + + bool isPopular = false; + if (isRatingValid && isStudentsValid) { + int ratingValue = int.parse(rating); + int studentsValue = int.parse(students); + + bool isSpecificRatingValid = int.tryParse(specificRating) != null; + bool isNumberOfRatingsValid = int.tryParse(numberOfRatings) != null; + + if (isSpecificRatingValid && isNumberOfRatingsValid) { + int specificRatingValue = int.parse(specificRating); + int numberOfRatingsValue = int.parse(numberOfRatings); + + isPopular = ratingValue > 4 && + studentsValue > 19 && + specificRatingValue > 4 && + numberOfRatingsValue > 4; + } + } + return Stack( + children: [ + Container( + width: getProportionateScreenWidth(155), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 1, + offset: Offset(7, 17), + ), + ], + ), + padding: EdgeInsets.only( + left: getProportionateScreenWidth(pad), + right: getProportionateScreenWidth(padRight), + ), + child: InkWell( + onTap: press, + child: Container( + width: getProportionateScreenWidth(40), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.primaryContainer, + spreadRadius: 11, + blurRadius: 0, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AspectRatio( + aspectRatio: 1.7, + child: CachedNetworkImage( + imageUrl: thumbnail, + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + decoration: BoxDecoration( + color: ninthColor, + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + ), + ), + gradient: LinearGradient( + stops: [0.2, 0.5, 0.6], + colors: [ninthColor, fourthColor, ninthColor]), + ), + errorWidget: (context, url, error) => Icon(Icons.error), + fadeInDuration: Duration(milliseconds: 1), + fadeOutDuration: Duration(milliseconds: 1), + ), + ), + SizedBox(height: getProportionateScreenWidth(4)), + Container( + padding: EdgeInsets.only(left: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(12), + fontWeight: bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: getProportionateScreenWidth(6)), + // Row( + // children: [product.description], + // ), + RichText( + text: new TextSpan( + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + color: Theme.of(context).dividerColor, + ), + children: <TextSpan>[ + new TextSpan( + text: '', + style: TextStyle( + fontWeight: bold, + ), + ), + new TextSpan( + text: instructorName, + style: TextStyle(fontWeight: bold), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenWidth(6)), + Row( + children: [ + Text( + specificRating, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: bold, + ), + ), + SizedBox( + width: getProportionateScreenWidth(2), + ), + RatingBarIndicator( + itemSize: getProportionateScreenWidth(11), + rating: double.parse(rating), + direction: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, _) => FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + ), + ), + SizedBox(width: getProportionateScreenWidth(3)), + Text( + '(' + numberOfRatings + ')', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: bold, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(6)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + price, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontSize: SizeConfig.blockHorizontal! * 3, + fontWeight: bold, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(6)), + if (realPrice.isNotEmpty && realPrice != price) + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + realPrice, + style: primaryTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + // IconButton(onPressed: whislistPress, icon: iconWishlist) + ], + ), + ) + else + SizedBox.shrink(), + if (isPopular) + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10), + ), + child: Container( + alignment: Alignment.center, + width: getProportionateScreenWidth(48), + height: getProportionateScreenHeight(17), + child: Text( + 'Populer', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: Colors.white, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: bold, + ), + ), + decoration: BoxDecoration( + color:themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + borderRadius: BorderRadius.circular(5), + ), + ), + ) + else + SizedBox.shrink(), + ], + ), + ) + ], + ), + ), + ), + ), + if (totalDiscount != 0) + Padding( + padding: EdgeInsets.only(top: getProportionateScreenHeight(7)), + child: Container( + height: 20, + alignment: Alignment.center, + width: getProportionateScreenWidth(35), + child: Text( + '${totalDiscount.toString()}%', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: baruTextutih, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: light, + ), + ), + decoration: BoxDecoration( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + borderRadius: BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + ), + ), + ) + else + SizedBox.shrink(), + ], + ); + } +} + +// Shimmer( +// child: Container( +// color: Colors.black, +// ), +// gradient: LinearGradient( +// stops: [0.2, 0.5, 0.6], +// colors: [ninthColor, fourthColor, ninthColor])), diff --git a/lib/screens/home/components/body_comp/product_card/product_card_big.dart b/lib/screens/home/components/body_comp/product_card/product_card_big.dart new file mode 100644 index 0000000..cf3b647 --- /dev/null +++ b/lib/screens/home/components/body_comp/product_card/product_card_big.dart @@ -0,0 +1,329 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../../../../theme.dart'; +import '../../../../../size_config.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +class ProductCardBig extends StatelessWidget { + const ProductCardBig({ + Key? key, + this.width = 120, + this.pad = 16, + this.padRight = 0, + required this.thumbnail, + required this.id, + required this.isTopCourse, + required this.title, + required this.instructorName, + required this.rating, + required this.specificRating, + required this.numberOfRatings, + required this.price, + required this.realPrice, + required this.press, + required this.students, + required this.totalDiscount, + }) : super(key: key); + + final double width; + final double pad, padRight; + final String thumbnail, + title, + instructorName, + price, + realPrice, + rating, + specificRating, + numberOfRatings, + id, + isTopCourse; + final String students; + final VoidCallback press; + final int totalDiscount; + // final VoidCallback? whislistPress; + // final Widget? iconWishlist; + + @override + Widget build(BuildContext context) { + //CourseProvider courseProvider = Provider.of<CourseProvider>(context); + // final formatCurrency = + // new NumberFormat.simpleCurrency(decimalDigits: 0, locale: 'id_ID'); + bool isRatingValid = int.tryParse(rating) != null; + bool isStudentsValid = int.tryParse(students) != null; + + bool isPopular = false; + if (isRatingValid && isStudentsValid) { + int ratingValue = int.parse(rating); + int studentsValue = int.parse(students); + + bool isSpecificRatingValid = int.tryParse(specificRating) != null; + bool isNumberOfRatingsValid = int.tryParse(numberOfRatings) != null; + + if (isSpecificRatingValid && isNumberOfRatingsValid) { + int specificRatingValue = int.parse(specificRating); + int numberOfRatingsValue = int.parse(numberOfRatings); + + isPopular = ratingValue > 4 && + studentsValue > 19 && + specificRatingValue > 4 && + numberOfRatingsValue > 4; + } + } + return Stack( + children: [ + Container( + height: getProportionateScreenHeight(500), + width: getProportionateScreenWidth(250), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 1, + offset: Offset(7, 17), + ), + ], + ), + padding: EdgeInsets.only( + left: getProportionateScreenWidth(pad), + right: getProportionateScreenWidth(padRight), + ), + child: InkWell( + onTap: press, + child: Container( + width: getProportionateScreenWidth(40), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.primaryContainer, + spreadRadius: 11, + blurRadius: 0, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AspectRatio( + aspectRatio: 1.7, + child: CachedNetworkImage( + imageUrl: thumbnail, + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.fill, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + decoration: BoxDecoration( + color: ninthColor, + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + ), + ), + gradient: LinearGradient( + stops: [0.2, 0.5, 0.6], + colors: [ninthColor, fourthColor, ninthColor]), + ), + errorWidget: (context, url, error) => Icon(Icons.error), + fadeInDuration: Duration(milliseconds: 1), + fadeOutDuration: Duration(milliseconds: 1), + ), + ), + SizedBox(height: getProportionateScreenWidth(4)), + Container( + padding: EdgeInsets.only(left: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(13), + fontWeight: bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: getProportionateScreenWidth(6)), + // Row( + // children: [product.description], + // ), + RichText( + text: new TextSpan( + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + color: Theme.of(context).dividerColor, + ), + children: [ + new TextSpan( + text: '', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + ), + ), + new TextSpan( + text: instructorName, + style: TextStyle(fontWeight: bold), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenWidth(6)), + Row( + children: [ + Text( + specificRating, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + fontWeight: bold, + ), + ), + SizedBox( + width: getProportionateScreenWidth(2), + ), + RatingBarIndicator( + itemSize: getProportionateScreenWidth(12), + rating: double.parse(rating), + direction: Axis.horizontal, + itemCount: 1, + itemBuilder: (context, _) => FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + ), + ), + SizedBox( + width: getProportionateScreenWidth(3), + ), + Text( + '(' + numberOfRatings + ' Reviews)', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + fontWeight: light, + ), + ), + ], + ), + SizedBox( + height: getProportionateScreenWidth(6), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + price, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontSize: SizeConfig.blockHorizontal! * 3, + fontWeight: bold, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(6)), + (realPrice != price) + ? Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + realPrice, + style: thirdTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + fontSize: + getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + + // IconButton( + // onPressed: whislistPress, + // icon: iconWishlist) + ], + ), + ) + : SizedBox(height: 13, width: 1), + if (isPopular) + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Container( + alignment: Alignment.center, + width: getProportionateScreenWidth(48), + height: getProportionateScreenHeight(17), + child: Text( + 'Populer', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: Colors.white, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: bold, + ), + ), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5), + ), + ), + ) + else + SizedBox(height: 0, width: 0), + ], + ), + ) + ], + ), + ), + ), + ), + if (totalDiscount != 0) + Padding( + padding: EdgeInsets.only(top: getProportionateScreenHeight(7)), + child: Container( + height: 20, + alignment: Alignment.center, + width: getProportionateScreenWidth(35), + child: Text( + '${totalDiscount.toString()}%', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: baruTextutih, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: light, + ), + ), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + ), + ), + ) + else + SizedBox(height: 0, width: 0), + ], + ); + } +} + +// Shimmer( +// child: Container( +// color: Colors.black, +// ), +// gradient: LinearGradient( +// stops: [0.2, 0.5, 0.6], +// colors: [ninthColor, fourthColor, ninthColor])), diff --git a/lib/screens/home/components/body_comp/product_card/product_card_coupon.dart b/lib/screens/home/components/body_comp/product_card/product_card_coupon.dart new file mode 100644 index 0000000..9f96ed6 --- /dev/null +++ b/lib/screens/home/components/body_comp/product_card/product_card_coupon.dart @@ -0,0 +1,320 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../../../../theme.dart'; +import '../../../../../size_config.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +class ProductCardCoupon extends StatelessWidget { + const ProductCardCoupon({ + Key? key, + this.width = 120, + this.pad = 16, + this.padRight = 0, + required this.thumbnail, + required this.id, + required this.isTopCourse, + required this.title, + required this.instructorName, + required this.rating, + required this.specificRating, + required this.numberOfRatings, + required this.price, + required this.realPrice, + required this.press, + required this.students, + required this.totalDiscount, + }) : super(key: key); + + final double width; + final double pad, padRight; + final String thumbnail, + title, + instructorName, + price, + realPrice, + rating, + specificRating, + numberOfRatings, + id, + isTopCourse; + final String students; + final VoidCallback press; + final int totalDiscount; + // final VoidCallback? whislistPress; + // final Widget? iconWishlist; + + @override + Widget build(BuildContext context) { + //CourseProvider courseProvider = Provider.of<CourseProvider>(context); + // final formatCurrency = + // new NumberFormat.simpleCurrency(decimalDigits: 0, locale: 'id_ID'); + bool isRatingValid = int.tryParse(rating) != null; + bool isStudentsValid = int.tryParse(students) != null; + + bool isPopular = false; + if (isRatingValid && isStudentsValid) { + int ratingValue = int.parse(rating); + int studentsValue = int.parse(students); + + bool isSpecificRatingValid = int.tryParse(specificRating) != null; + bool isNumberOfRatingsValid = int.tryParse(numberOfRatings) != null; + + if (isSpecificRatingValid && isNumberOfRatingsValid) { + int specificRatingValue = int.parse(specificRating); + int numberOfRatingsValue = int.parse(numberOfRatings); + + isPopular = ratingValue > 4 && + studentsValue > 19 && + specificRatingValue > 4 && + numberOfRatingsValue > 4; + } + } + return Stack( + children: [ + Container( + width: getProportionateScreenWidth(165), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 1, + offset: Offset(7, 17), + ), + ], + ), + padding: EdgeInsets.only( + left: getProportionateScreenWidth(pad), + right: getProportionateScreenWidth(padRight), + ), + child: InkWell( + onTap: press, + child: Container( + width: getProportionateScreenWidth(40), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.primaryContainer, + spreadRadius: 11, + blurRadius: 0, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AspectRatio( + aspectRatio: 1.7, + child: CachedNetworkImage( + imageUrl: thumbnail, + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + decoration: BoxDecoration( + color: ninthColor, + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + ), + ), + gradient: LinearGradient( + stops: [0.2, 0.5, 0.6], + colors: [ninthColor, fourthColor, ninthColor]), + ), + errorWidget: (context, url, error) => Icon(Icons.error), + fadeInDuration: Duration(milliseconds: 1), + fadeOutDuration: Duration(milliseconds: 1), + ), + ), + SizedBox(height: getProportionateScreenWidth(4)), + Container( + padding: EdgeInsets.only(left: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(12), + fontWeight: bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: getProportionateScreenWidth(6)), + // Row( + // children: [product.description], + // ), + RichText( + text: new TextSpan( + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + color: Theme.of(context).dividerColor, + ), + children: <TextSpan>[ + new TextSpan( + text: '', + style: TextStyle( + fontWeight: bold, + ), + ), + new TextSpan( + text: instructorName, + style: TextStyle(fontWeight: bold), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenWidth(6)), + Row( + children: [ + Text( + specificRating, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: bold, + ), + ), + SizedBox( + width: getProportionateScreenWidth(2), + ), + RatingBarIndicator( + itemSize: getProportionateScreenWidth(11), + rating: double.parse(rating), + direction: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, _) => FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + ), + ), + SizedBox(width: getProportionateScreenWidth(3)), + Text( + '(' + numberOfRatings + ')', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: bold, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(6)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + price, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontSize: SizeConfig.blockHorizontal! * 3, + fontWeight: bold, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(6)), + if (realPrice.isNotEmpty && realPrice != price) + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + realPrice, + style: primaryTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + // IconButton(onPressed: whislistPress, icon: iconWishlist) + ], + ), + ) + else + SizedBox.shrink(), + if (isPopular) + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Container( + alignment: Alignment.center, + width: getProportionateScreenWidth(48), + height: getProportionateScreenHeight(17), + child: Text( + 'Populer', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: Colors.white, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: bold, + ), + ), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.circular(5), + ), + ), + ) + else + SizedBox(height: 0, width: 0), + ], + ), + ) + ], + ), + ), + ), + ), + if (totalDiscount != 0) + Padding( + padding: EdgeInsets.only(top: getProportionateScreenHeight(7)), + child: Container( + height: 20, + alignment: Alignment.center, + width: getProportionateScreenWidth(35), + child: Text( + '${totalDiscount.toString()}%', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: baruTextutih, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: light, + ), + ), + decoration: BoxDecoration( + color: primaryColor, + borderRadius: BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + ), + ), + ) + else + SizedBox(height: 0, width: 0), + ], + ); + } +} + +// Shimmer( +// child: Container( +// color: Colors.black, +// ), +// gradient: LinearGradient( +// stops: [0.2, 0.5, 0.6], +// colors: [ninthColor, fourthColor, ninthColor])), diff --git a/lib/screens/home/components/body_comp/product_card/product_card_new.dart b/lib/screens/home/components/body_comp/product_card/product_card_new.dart new file mode 100644 index 0000000..fb9680b --- /dev/null +++ b/lib/screens/home/components/body_comp/product_card/product_card_new.dart @@ -0,0 +1,328 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import '../../../../../providers/theme_provider.dart'; +import '../../../../../theme.dart'; +import '../../../../../size_config.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +class ProductCardNew extends StatelessWidget { + const ProductCardNew({ + Key? key, + this.width = 120, + this.pad = 16, + this.padRight = 0, + required this.thumbnail, + required this.id, + required this.isTopCourse, + required this.title, + required this.instructorName, + required this.rating, + required this.specificRating, + required this.numberOfRatings, + required this.price, + required this.realPrice, + required this.press, + required this.students, + required this.totalDiscount, + }) : super(key: key); + + final double width; + final double pad, padRight; + final String thumbnail, + title, + instructorName, + price, + realPrice, + rating, + specificRating, + numberOfRatings, + id, + isTopCourse; + final String students; + final VoidCallback press; + final int totalDiscount; + // final VoidCallback? whislistPress; + // final Widget? iconWishlist; + + @override + Widget build(BuildContext context) { + //CourseProvider courseProvider = Provider.of<CourseProvider>(context); + // final formatCurrency = + // new NumberFormat.simpleCurrency(decimalDigits: 0, locale: 'id_ID'); + bool isRatingValid = int.tryParse(rating) != null; + bool isStudentsValid = int.tryParse(students) != null; + final themeProvider = Provider.of<ThemeProvider>(context); + + bool isPopular = false; + if (isRatingValid && isStudentsValid) { + int ratingValue = int.parse(rating); + int studentsValue = int.parse(students); + + bool isSpecificRatingValid = int.tryParse(specificRating) != null; + bool isNumberOfRatingsValid = int.tryParse(numberOfRatings) != null; + + if (isSpecificRatingValid && isNumberOfRatingsValid) { + int specificRatingValue = int.parse(specificRating); + int numberOfRatingsValue = int.parse(numberOfRatings); + + isPopular = ratingValue > 4 && + studentsValue > 19 && + specificRatingValue > 4 && + numberOfRatingsValue > 4; + } + } + return Stack( + children: [ + Container( + width: getProportionateScreenWidth(155), + height: getProportionateScreenHeight(200), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 1, + blurRadius: 1, + offset: Offset(7, 17), + ), + ], + ), + padding: EdgeInsets.only( + left: getProportionateScreenWidth(pad), + right: getProportionateScreenWidth(padRight), + ), + child: InkWell( + onTap: press, + child: Container( + width: getProportionateScreenWidth(40), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.primaryContainer, + spreadRadius: 11, + blurRadius: 0, + offset: Offset(0, 3), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AspectRatio( + aspectRatio: 1.7, + child: CachedNetworkImage( + imageUrl: thumbnail, + imageBuilder: (context, imageProvider) => Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.cover, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + decoration: BoxDecoration( + color: ninthColor, + borderRadius: + BorderRadius.vertical(top: Radius.circular(5)), + ), + ), + gradient: LinearGradient( + stops: [0.2, 0.5, 0.6], + colors: [ninthColor, fourthColor, ninthColor]), + ), + errorWidget: (context, url, error) => Icon(Icons.error), + fadeInDuration: Duration(milliseconds: 1), + fadeOutDuration: Duration(milliseconds: 1), + ), + ), + SizedBox(height: getProportionateScreenWidth(4)), + Container( + padding: EdgeInsets.only(left: 2), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(12), + fontWeight: bold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: getProportionateScreenWidth(6)), + // Row( + // children: [product.description], + // ), + RichText( + text: new TextSpan( + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + color: Theme.of(context).dividerColor, + ), + children: <TextSpan>[ + new TextSpan( + text: '', + style: TextStyle( + fontWeight: bold, + ), + ), + new TextSpan( + text: instructorName, + style: TextStyle(fontWeight: bold), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenWidth(6)), + Row( + children: [ + if (specificRating.isNotEmpty && specificRating != '0') ...[ + Text( + specificRating, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: bold, + ), + ), + SizedBox( + width: getProportionateScreenWidth(2), + ),], + RatingBarIndicator( + itemSize: getProportionateScreenWidth(11), + rating: (rating.isEmpty || double.tryParse(rating) == 0) + ? 5.0 + : double.parse(rating), // Jika rating kosong atau 0, tampilkan bintang 5 + direction: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, _) => FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + ), + ), + SizedBox(width: getProportionateScreenWidth(3)), + if (numberOfRatings.isNotEmpty && numberOfRatings != '0') ...[ + Text( + '(' + numberOfRatings + ')', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + fontWeight: bold, + ), + ), + ], + ], + ), + SizedBox(height: getProportionateScreenWidth(6)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + price, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontSize: SizeConfig.blockHorizontal! * 3, + fontWeight: bold, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(6)), + if (realPrice.isNotEmpty && realPrice != price) + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + realPrice, + style: primaryTextStyle.copyWith( + decoration: TextDecoration.lineThrough, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + // IconButton(onPressed: whislistPress, icon: iconWishlist) + ], + ), + ) + else + SizedBox.shrink(), + Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10)), + child: Container( + alignment: Alignment.center, + width: getProportionateScreenWidth(48), + height: getProportionateScreenHeight(17), + child: Text( + 'NEW', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: Colors.white, + fontSize: SizeConfig.blockHorizontal! * 3, + fontWeight: reguler, + ), + ), + decoration: BoxDecoration( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + borderRadius: BorderRadius.circular(5), + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ), + if (totalDiscount != 0) + Padding( + padding: EdgeInsets.only(top: getProportionateScreenHeight(7)), + child: Container( + height: 20, + alignment: Alignment.center, + width: getProportionateScreenWidth(35), + child: Text( + '${totalDiscount.toString()}%', + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + color: baruTextutih, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: light, + ), + ), + decoration: BoxDecoration( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + borderRadius: BorderRadius.only( + topRight: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + ), + ), + ) + else + SizedBox(height: 0, width: 0), + ], + ); + } +} + +// Shimmer( +// child: Container( +// color: Colors.black, +// ), +// gradient: LinearGradient( +// stops: [0.2, 0.5, 0.6], +// colors: [ninthColor, fourthColor, ninthColor])), diff --git a/lib/screens/home/components/body_comp/product_card/product_card_program.dart b/lib/screens/home/components/body_comp/product_card/product_card_program.dart new file mode 100644 index 0000000..692e2f5 --- /dev/null +++ b/lib/screens/home/components/body_comp/product_card/product_card_program.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/ProductProgram.dart'; +import '../../../../../theme.dart'; +import '../../../../../size_config.dart'; + +class ProductCardProgram extends StatelessWidget { + const ProductCardProgram({ + Key? key, + this.width = 150, + this.aspectRetio = 1.02, + required this.product, + }) : super(key: key); + + final double width, aspectRetio; + final ProductProgram product; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EdgeInsets.fromLTRB(0, getProportionateScreenWidth(10), + getProportionateScreenWidth(10), getProportionateScreenWidth(10)), + child: SizedBox( + width: getProportionateScreenWidth(width), + child: GestureDetector( + onTap: () {}, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: 1.7, + child: Container( + decoration: BoxDecoration( + color: tenthColor.withOpacity(0), + borderRadius: BorderRadius.circular(15), + ), + child: Hero( + tag: product.id.toString(), + child: Image.asset(product.images[0], scale: 0.000000001), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/home/components/body_comp/program.dart b/lib/screens/home/components/body_comp/program.dart new file mode 100644 index 0000000..582a8c7 --- /dev/null +++ b/lib/screens/home/components/body_comp/program.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'product_card/product_card_program.dart'; +import 'package:initial_folder/models/ProductProgram.dart'; + +import '../../../../size_config.dart'; +import '../../../../theme.dart'; + +class Program extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(20)), + child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Text('Promo Terkini', + textAlign: TextAlign.left, + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + color: tenthColor, + fontSize: getProportionateScreenWidth(14), + fontWeight: semiBold)), + ]), + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(20)), + child: Row( + children: [ + ...List.generate( + demoProducts.length, + (index) => ProductCardProgram(product: demoProducts[index]), + ), + ], + ), + ), + ) + ], + ); + } +} diff --git a/lib/screens/home/components/body_comp/promo_course.dart b/lib/screens/home/components/body_comp/promo_course.dart new file mode 100644 index 0000000..dfdb9f0 --- /dev/null +++ b/lib/screens/home/components/body_comp/promo_course.dart @@ -0,0 +1,364 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/promo_course_provider.dart'; +// import 'package:initial_folder/providers/top_course_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../../../size_config.dart'; +import '../../../../theme.dart'; + +class PromoCourse extends StatelessWidget { + const PromoCourse({Key? key, this.text}) : super(key: key); + final String? text; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + text.toString(), + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(10)), + Consumer<PromoCourseProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else if (state.state == ResultState.HasData) { + return Container( + alignment: Alignment.centerLeft, + margin: EdgeInsets.only(left: getProportionateScreenWidth(10)), + height: getProportionateScreenHeight(220), + child: ListView.builder( + scrollDirection: Axis.horizontal, + physics: ScrollPhysics(), + shrinkWrap: true, + itemCount: state.result.length, + itemBuilder: (context, index) { + var promoCourse = state.result[index]; + // var finalRating = double.parse( + // (promoCourse.specificRating![0] / 20).toStringAsFixed(2)); + int price = + int.tryParse(promoCourse.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse( + promoCourse.discountPrice.replaceAll('.', '')) ?? + 0; + + int calculatedPrice = (promoCourse.discountPrice != '0') + ? price - discountPrice + : price; + + String displayedPrice = (calculatedPrice == 0) + ? promoCourse.price + : calculatedPrice.toString(); + return Container( + margin: EdgeInsets.only( + top: getProportionateScreenHeight(10), + ), + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(18), + bottom: getProportionateScreenWidth(18), + ), + child: ProductCard( + totalDiscount: promoCourse.totalDiscount ?? 0, + students: promoCourse.students ?? '0', + pad: 12, + id: promoCourse.idCourse, + thumbnail: promoCourse.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: promoCourse.title, + instructorName: promoCourse.instructorName, + specificRating: (promoCourse.rating.isNotEmpty && + promoCourse.rating[0]?.avgRating != null) + ? promoCourse.rating[0]!.avgRating.toString() + : '0', + rating: (promoCourse.rating.isNotEmpty && + promoCourse.rating[0]?.avgRating != null) + ? promoCourse.rating[0]!.avgRating.toString() + : '5.0', + numberOfRatings: (promoCourse.rating.isNotEmpty && + promoCourse.rating[0]?.totalReview != null) + ? promoCourse.rating[0]!.totalReview! + : '0', + isTopCourse: promoCourse.topCourse!, + price: (promoCourse.price == '0') + ? 'Gratis' + : (promoCourse.promoPrice != '0') + ? numberFormat(promoCourse.promoPrice) + : numberFormat(displayedPrice), + realPrice: (promoCourse.price == '0') + ? '' + : numberFormat(promoCourse.price), + press: () { + print(promoCourse.idCourse); + Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: DetailCourseScreen( + isPromo: true, + idcourse: promoCourse.idCourse, + ), + ), + ); + }, + ), + ), + ); + }, + ), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 105, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 190, + color: Colors.white, + )), + SizedBox(height: 10), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 120, + color: Colors.white, + )), + SizedBox(height: 8), + Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Container( + height: 15, + width: 85, + color: Colors.white, + ), + ), + ], + ), + ) + ], + ); + } else { + return Center(child: Text('')); + } + }, + ) + ], + ); + } +} diff --git a/lib/screens/home/components/body_comp/recommendation.dart b/lib/screens/home/components/body_comp/recommendation.dart new file mode 100644 index 0000000..3112765 --- /dev/null +++ b/lib/screens/home/components/body_comp/recommendation.dart @@ -0,0 +1,41 @@ +// import 'package:flutter/material.dart'; +// import 'product_card/product_card.dart'; +// import 'package:initial_folder/models/Product.dart'; + +// import '../../../../size_config.dart'; +// import 'section_title.dart'; + +// class Recommendation extends StatelessWidget { +// @override +// Widget build(BuildContext context) { +// return Column( +// children: [ +// Padding( +// padding: +// EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(20)), +// child: SectionTitle(title: "Rekomendasi Khusus", press: () {}), +// ), +// SizedBox(height: 10), +// SingleChildScrollView( +// scrollDirection: Axis.horizontal, +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// ...List.generate( +// demoProducts.length, +// (index) { +// if (demoProducts[index].isPopular) +// return ProductCard(product: demoProducts[index]); + +// return SizedBox +// .shrink(); // here by default width and height is 0 +// }, +// ), +// SizedBox(width: getProportionateScreenWidth(200)), +// ], +// ), +// ) +// ], +// ); +// } +// } diff --git a/lib/screens/home/components/body_comp/search_field.dart b/lib/screens/home/components/body_comp/search_field.dart new file mode 100644 index 0000000..b20c0df --- /dev/null +++ b/lib/screens/home/components/body_comp/search_field.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/widgets/search_and_filter_course.dart'; +import '../../../../theme.dart'; +import '../../../../size_config.dart'; + +class SearchField extends StatelessWidget { + const SearchField({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + // width: SizeConfig.screenWidth * 0.9, + // height: 33, + // decoration: BoxDecoration( + // color: Colors.white, + // borderRadius: BorderRadius.circular(15), + // border: Border.all( + // color: kSecondaryColor.withOpacity(0.5), + // width: 2, + //print(SizeConfig.screenWidth); + return GestureDetector( + onTap: () => Navigator.push(context, + MaterialPageRoute(builder: (context) => SearchAndFilterCourse())), + child: Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + height: 35, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + border: Border.all(color: secondaryColor)), + child: Column( + children: [ + Expanded( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 13), + child: Icon( + Icons.search, + size: 20, + color: secondaryColor, + ), + ), + SizedBox( + width: getProportionateScreenWidth(10), + ), + Text( + 'Cari kursus', + style: secondaryTextStyle.copyWith( + color: secondaryColor, fontSize: 12), + ) + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/home/components/body_comp/section_title.dart b/lib/screens/home/components/body_comp/section_title.dart new file mode 100644 index 0000000..3a02620 --- /dev/null +++ b/lib/screens/home/components/body_comp/section_title.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +import '../../../../size_config.dart'; +import '../../../../theme.dart'; + +class SectionTitle extends StatelessWidget { + const SectionTitle({ + Key? key, + required this.title, + required this.press, + }) : super(key: key); + + final String title; + final GestureTapCallback press; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold), + ), + GestureDetector( + onTap: press, + child: Text( + "Lihat Semua", + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(11), + fontWeight: reguler), + ), + ), + ], + ); + } +} diff --git a/lib/screens/home/components/home_page.dart b/lib/screens/home/components/home_page.dart new file mode 100644 index 0000000..8bb9c1b --- /dev/null +++ b/lib/screens/home/components/home_page.dart @@ -0,0 +1,279 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/providers/banners_provider.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/categories_provider.dart'; +import 'package:initial_folder/providers/course_by_category_provider.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/latest_course_provider.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/others_course_provider.dart'; +import 'package:initial_folder/providers/promo_course_provider.dart'; +import 'package:initial_folder/providers/promo_course_provider.dart' as promo; +import 'package:initial_folder/providers/top_course_provider.dart'; +import 'package:initial_folder/screens/cart/cart_page.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/screens/home/components/appBar/home_header.dart'; +import 'package:initial_folder/screens/home/components/body_comp/certificate_voucher.dart'; +import 'package:initial_folder/screens/home/components/body_comp/course_by_category.dart'; +import 'package:initial_folder/screens/home/components/body_comp/home_categories.dart'; +import 'package:initial_folder/screens/home/components/body_comp/latest_course.dart'; +import 'package:initial_folder/screens/home/components/body_comp/lihat_semua_kategori.dart'; +import 'package:initial_folder/screens/home/components/body_comp/populer_course.dart'; +import 'package:initial_folder/screens/home/components/body_comp/promo_course.dart'; +import 'package:initial_folder/services/categories_service.dart'; +import 'package:initial_folder/services/course_by_category_service.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:provider/provider.dart'; +import '../../../size_config.dart'; +import '../../../theme.dart'; +import 'body_comp/others_course.dart'; +import 'body_comp/carousel.dart'; + + +class HomePage extends StatefulWidget { + static String routeName = "/homepage"; + HomePage({ + Key? key, + }) : super(key: key); + + @override + State<HomePage> createState() => _HomePageState(); +} + +class _HomePageState extends State<HomePage> { + ScrollController _controller = ScrollController(); + GlobalKey<NavigatorState> homeKey = GlobalKey<NavigatorState>(); + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of<CartsProvider>(context, listen: false).getCarts(); + }); + _controller.addListener(() { + onScrol(); + }); + super.initState(); + + FirebaseMessaging.instance.getInitialMessage().then((message) { + if (message?.data['route'] == "/cart") { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CartPage(), + )); + } else if (message?.data['route'] == "/play_course") { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => LessonCourseProvider( + lessonCourseService: LessonCourseService(), + id: int.parse(message?.data['id_course'] ?? '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), + id: message?.data['id_course'] ?? '1')) + ], + child: PlayCourse( + judul: message?.data['title'] ?? '', + instruktur: message?.data['instructur_name'] ?? '', + thumbnail: message?.data['thumbnail'] ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + courseeid: message?.data['id_course'] ?? '', + isQna: true, + ), + ), + ), + ); + } + }); + } + + void onScrol() async { + double maxScroll = _controller.position.maxScrollExtent; + double currentScroll = _controller.position.pixels; + if (maxScroll == currentScroll) { + await Provider.of<OthersCourseProvider>(context, listen: false) + .getOthersCourses(); + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void onRefresh() async { + Provider.of<OthersCourseProvider>(context, listen: false) + .getOthersCourses(); + Provider.of<BannersProvider>(context, listen: false).getAllBanners(); + Provider.of<PromoCourseProvider>(context, listen: false).getPromoCourse(); + Provider.of<TopCourseProvider>(context, listen: false).getTopCourse(); + Provider.of<LatestCourseProvider>(context, listen: false).getLatestCourse(); + Provider.of<OthersCourseProvider>(context, listen: false).getOthersCourse(); + setState(() { + print('Reloading page...'); + }); + } + + @override + Widget build(BuildContext context) { + if (_controller.hasClients) { + if (_controller.position.pixels != _controller.position.maxScrollExtent) { + _controller.animateTo(0, + duration: Duration(milliseconds: 900), curve: Curves.linear); + } + } + return Navigator( + key: homeKey, + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + settings: settings, + builder: (BuildContext context) { + if (settings.name == '/lihatSemuaKategori') { + return LihatSemuaKategori(); + } + + return Container( + color: Theme.of(context).colorScheme.background, + child: SafeArea( + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + appBar: PreferredSize( + preferredSize: + Size.fromHeight(getProportionateScreenHeight(60)), + child: HomeHeader(), + ), + body: RefreshIndicator( + onRefresh: () async { + setState(() { + print('loding test'); + onRefresh(); + }); + }, + child: ListView( + controller: _controller, + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + // Stack( + // children: [ + // // Container( + // // height: getProportionateScreenWidth(140), + // // decoration: BoxDecoration( + // // image: DecorationImage( + // // image: AssetImage('assets/images/VectorBG.png'), + // // fit: BoxFit.fill, + // // alignment: AlignmentDirectional.topCenter, + // // ), + // // ), + // // ), + // Container( + // padding: EdgeInsets.fromLTRB( + // getProportionateScreenWidth(18.0), + // getProportionateScreenWidth(11.5), + // 0, + // 0), + // child: Begin()), + // SizedBox(height: getProportionateScreenWidth(24)), + // Container( + // // padding: + // // EdgeInsets.only(top: getProportionateScreenWidth(110)), + // //height: getProportionateScreenWidth(33), + // alignment: Alignment.bottomCenter, + // child: SearchField(), + // ), + // ], + // ), + // Container( + // padding: EdgeInsets.fromLTRB(getProportionateScreenWidth(18.0), + // getProportionateScreenWidth(11.5), 0, 0), + // child: Begin()), + // SizedBox(height: getProportionateScreenWidth(36)), + + //SearchField(), + + // SizedBox(height: getProportionateScreenWidth(12)), + CarouselWithIndicatorDemo(), + + // Padding( + // padding: EdgeInsets.symmetric( + // horizontal: getProportionateScreenWidth(14)), + // child: Center(child: GopayVoucher()), + // ), + // SizedBox(height: 18), + // Program(), + HomeCategories(), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(14)), + child: CertificateVoucher(), + ), + SizedBox(height: getProportionateScreenHeight(19)), + // Padding( + // padding: EdgeInsets.symmetric( + // horizontal: getProportionateScreenWidth(20)), + // child: + // Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + // Text('Pencarian Terpopuler', + // textAlign: TextAlign.left, + // style: secondaryTextStyle.copyWith( + // letterSpacing: 1, + // color: tenthColor, + // fontSize: getProportionateScreenWidth(14), + // fontWeight: semiBold)), + // ]), + // ), + // SizedBox(height: 10), + // Categories(), + Consumer<PromoCourseProvider>( + builder: (context, promoState, _) { + SizedBox(height: 20); + return promoState.state == promo.ResultState.HasData + ? PromoCourse(text: "Promo") + : SizedBox(); + }, + ), + + SizedBox(height: 20), + + PopulerCourse(text: "Kursus Teratas"), + LatestCourse(text: "Kursus Terbaru"), + SizedBox(height: getProportionateScreenHeight(25)), + OthersCourse(), + Provider.of<OthersCourseProvider>(context).loading + ? Center( + child: SizedBox( + height: 30, + width: 30, + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ), + ) + : SizedBox(height: 30), + SizedBox(height: 25), + // ChangeNotifierProvider<CourseProvider>.value( + // value: CourseProvider(courseService: CourseService()), + // child: Course()), + + // Recommendation(), + ], + ), + ), + ), + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/screens/home/components/notification.dart b/lib/screens/home/components/notification.dart new file mode 100644 index 0000000..3770f0b --- /dev/null +++ b/lib/screens/home/components/notification.dart @@ -0,0 +1,429 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/notification.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/notification_provider.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/services/notification_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class Notifikasi extends StatelessWidget { + const Notifikasi({Key? key}) : super(key: key); + static String routeName = "/notifikasi"; + + initNotif(BuildContext context) async { + FirebaseMessaging.onMessage.listen((event) async { + if (event.data.isNotEmpty) { + await Provider.of<NotificationProvider>(context, listen: false) + .getMyNotification(); + await Provider.of<NotificationProvider>(context, listen: false) + .getNotificationCount(); + } + }); + Future.delayed(const Duration(seconds: 0), () async { + await Provider.of<NotificationProvider>(context, listen: false) + .getMyNotification(); + await Provider.of<NotificationProvider>(context, listen: false) + .getNotificationCount(); + }); + } + + readAllHandler( + BuildContext context, + ) async { + await Provider.of<NotificationProvider>(context, listen: false) + .markAsRead(); + if (!context.mounted) return; + Provider.of<NotificationProvider>(context, listen: false).changeAllRead(); + } + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + initNotif(context); + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + scrolledUnderElevation: 0.0, + actions: [ + IconButton( + tooltip: "Mark as Read", + onPressed: () => readAllHandler( + context, + ), + icon: const Icon(Icons.drafts), + ), + ], + centerTitle: true, + title: Text( + 'Notifikasi', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + body: RefreshIndicator( + displacement: 40, + onRefresh: () async { + await Provider.of<NotificationProvider>(context, listen: false) + .getMyNotification(); + }, + child: Consumer<NotificationProvider>( + builder: (context, value, child) { + if (value.state == resultState.Loading) { + return Shimmer.fromColors( + baseColor: Colors.grey, + highlightColor: Colors.white, + child: ListView.builder( + itemCount: 6, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(10)), + child: Container( + width: double.infinity, + padding: + EdgeInsets.all(getProportionateScreenWidth(10)), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + bottom: BorderSide(color: Colors.white))), + child: Row( + children: [ + SizedBox( + height: getProportionateScreenHeight(50), + child: Container( + color: Colors.white, + width: getProportionateScreenWidth(100), + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Expanded( + flex: 3, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + color: Colors.white, + height: getProportionateScreenHeight(2), + ), + SizedBox( + height: getProportionateScreenHeight(15), + ), + Container( + color: Colors.white, + height: getProportionateScreenHeight(15), + ), + SizedBox( + height: getProportionateScreenHeight(5), + ), + Container( + color: Colors.white, + height: getProportionateScreenHeight(15), + ), + SizedBox( + height: getProportionateScreenHeight(5), + ), + Container( + color: Colors.white, + height: getProportionateScreenHeight(15), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ), + ); + } else if (value.state == resultState.Error) { + return Center( + child: ListView( + children: [ + Container( + padding: const EdgeInsets.all(20.0), + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height / 1.5), + child: Center( + child: Text('Terjadi Kesalahan'), + ), + ) + ], + ), + ); + } else if (value.state == resultState.NoData) { + return Center( + child: ListView( + children: [ + Container( + padding: const EdgeInsets.all(20.0), + constraints: BoxConstraints( + minHeight: MediaQuery.of(context).size.height / 1.5), + child: Center( + child: Text('Notifikasi Kosong'), + ), + ) + ], + ), + ); + } else { + var allResults = [...value.result, ...value.resultAnnouncement]; + return ListView.builder( + itemCount: allResults.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () async { + Provider.of<NotificationProvider>(context, + listen: false) + .changeIsRead(value.result, index); + await NotificationServices().readNotification( + value.result[index].idRead!, + value.result[index].ket!); + if (!context.mounted) return; + Navigator.of(context, rootNavigator: true).push( + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => LessonCourseProvider( + lessonCourseService: LessonCourseService(), + id: int.parse( + value.result[index].idCourse ?? '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), + id: value.result[index].idCourse ?? '1', + ), + ) + ], + child: PlayCourse( + judul: value.result[index].titleCourse ?? '', + instruktur: + value.result[index].instructor ?? '', + thumbnail: value.result[index].thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + courseeid: value.result[index].idCourse ?? '', + isQna: (value.result[index].subject == + "Q&A Kursus") + ? true + : false, + ), + ), + ), + ); + }, + child: Padding( + padding: EdgeInsets.fromLTRB( + getProportionateScreenWidth(20), + getProportionateScreenHeight(5), + getProportionateScreenWidth(20), + getProportionateScreenHeight(5)), + child: Container( + width: double.infinity, + padding: + EdgeInsets.all(getProportionateScreenWidth(10)), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: + isDarkMode ? Colors.black : Colors.grey, + spreadRadius: 0.01, + blurRadius: 2, + offset: Offset(0, 1), // Shadow position + ), + ], + color: Theme.of(context) + .colorScheme + .primaryContainer, + border: Border( + bottom: BorderSide(color: Colors.white12))), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox( + height: getProportionateScreenHeight(60), + child: (value.result[index].thumbnail == "" || + value.result[index].thumbnail == null) + ? Image.network( + 'https://api.vokasia.id/images/default-thumbnail.png', + fit: BoxFit.fill, + width: + getProportionateScreenWidth(120), + ) + : Image.network( + value.result[index].thumbnail!, + fit: BoxFit.fill, + width: + getProportionateScreenWidth(120), + )), + SizedBox(width: getProportionateScreenWidth(10)), + Expanded( + flex: 3, + child: Column( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + value.result[index].subject!, + style: thirdTextStyle.copyWith( + fontSize: 12, + fontWeight: reguler, + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + SizedBox( + height: getProportionateScreenHeight(15), + child: Row( + children: [ + Text( + DateFormat('dd MMMM yyyy ').format( + DateTime + .fromMillisecondsSinceEpoch( + int.parse(value + .result[index] + .timestamps!) * + 1000), + ), + style: thirdTextStyle.copyWith( + fontSize: 12, + fontWeight: reguler, + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: + getProportionateScreenWidth( + 5)), + child: Icon(Icons.lens_rounded, + color: Color(0xffc4c4c4), + size: + getProportionateScreenWidth( + 7)), + ), + Text( + value.result[index].date! + .substring( + value.result[index].date! + .length - + 8, + value.result[index].date! + .length - + 3), + style: thirdTextStyle.copyWith( + fontSize: 12, + fontWeight: reguler, + color: Theme.of(context) + .colorScheme + .onPrimary, + )), + SizedBox( + width: + getProportionateScreenWidth( + 10)), + notificationPing(value.result[index]) + ], + ), + ), + + RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: (value.result[index].messages != + null) + ? value.result[index].messages! + : "", + style: thirdTextStyle.copyWith( + fontSize: 12, + fontWeight: reguler, + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + )), + // Text( + // (value.result[index].messages != null) + // ? value.result[index].messages! + // : "", + // style: thirdTextStyle.copyWith( + // fontSize: 12, + // fontWeight: reguler, + // color: Theme.of(context) + // .colorScheme + // .onPrimary, + // ), + // ), + SizedBox( + height: + getProportionateScreenHeight(5)), + RichText( + overflow: TextOverflow.ellipsis, + maxLines: 2, + text: TextSpan( + text: + value.result[index].titleCourse!, + style: thirdTextStyle.copyWith( + fontSize: 12, + fontWeight: reguler, + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + )), + // Text( + // value.result[index].titleCourse!, + // style: thirdTextStyle.copyWith( + // fontSize: + // getProportionateScreenHeight(10)), + // ), + ], + ), + ), + ], + ), + ), + )); + }, + ); + } + }, + ), + ), + ); + } + + Widget notificationPing(NotificationData data) { + if (data.isRead == "0") { + return Icon(Icons.lens_rounded, + color: Color(0xffCD2228), size: getProportionateScreenWidth(10)); + } else { + return SizedBox(); + } + } +} diff --git a/lib/screens/home/components/notifikasi.dart b/lib/screens/home/components/notifikasi.dart new file mode 100644 index 0000000..737d144 --- /dev/null +++ b/lib/screens/home/components/notifikasi.dart @@ -0,0 +1,68 @@ +// import 'package:flutter/material.dart'; +// import 'package:initial_folder/providers/history_transactions_provider.dart'; +// import 'package:initial_folder/services/notification_service.dart'; +// import 'package:initial_folder/size_config.dart'; +// import 'package:initial_folder/theme.dart'; +// import 'package:initial_folder/widgets/notifikasi_list.dart'; +// import 'package:provider/provider.dart'; +// +// class Notifikasi extends StatelessWidget { +// const Notifikasi({Key? key}) : super(key: key); +// +// @override +// Widget build(BuildContext context) { +// NotificationServices().getNotification(); +// return SafeArea( +// child: Scaffold( +// appBar: AppBar( +// centerTitle: true, +// title: Text( +// 'Notifikasi', +// style: secondaryTextStyle.copyWith( +// letterSpacing: 2, +// fontWeight: semiBold, +// fontSize: getProportionateScreenWidth(14)), +// )), +// body: Consumer<HistoryTranscationsProvider>( +// builder: (context, state, _) { +// if (state.state == ResultState.loading) { +// return Center( +// child: CircularProgressIndicator( +// color: primaryColor, +// ), +// ); +// } else if (state.state == ResultState.error) { +// return Center( +// child: Text('Terjadi Kesalahan'), +// ); +// } else if (state.state == ResultState.noData) { +// return Center( +// child: Text('Terjadi Kesalahan'), +// ); +// } else { +// // var data = state.historyPayment! +// // .where((item) => +// // // item.statusPayment == 'Success' || +// // item.statusPayment != 'Failed') +// // .toList(); +// var pending = state.paymentPending!; +// var success = state.historyPayment! +// .where((element) => element.statusPayment == 'Success') +// .toList(); +// var data = pending + success; +// // var datas = state.historyPayment.removeWhere((element) => element.statusPayment != 'Success') +// return ListView.builder( +// itemCount: data.length > 10 ? 10 : data.length, +// itemBuilder: (context, index) { +// return NotifikasiList( +// data: data[index], +// baru: index < 3 ? true : false, +// ); +// }, +// ); +// } +// }, +// )), +// ); +// } +// } diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart new file mode 100644 index 0000000..b36111c --- /dev/null +++ b/lib/screens/home/home_screen.dart @@ -0,0 +1,174 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/screens/course/my_course_page.dart'; +import 'package:initial_folder/screens/profile/profile_page.dart'; +import 'package:initial_folder/screens/search_course/search_page.dart'; +import 'package:initial_folder/screens/whislist/my_whislist_page.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'components/home_page.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({Key? key}) : super(key: key); + static String routeName = "/home"; + + @override + State<HomeScreen> createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State<HomeScreen> { + Widget buildNavItem(int index, String icon, String activeIcon, String title) { + PageProvider pageProvider = Provider.of<PageProvider>(context); + return GestureDetector( + onTap: () { + pageProvider.currentIndex = index; + }, + child: Container( + width: getProportionateScreenWidth(360) / 5, + decoration: BoxDecoration( + color: pageProvider.currentIndex == index + ? Theme.of(context).brightness == Brightness.light + ? primaryColorligtmode + : primaryColor + : Colors.transparent, + borderRadius: BorderRadius.circular(5), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimatedContainer( + duration: Duration(milliseconds: 300), + height: getProportionateScreenHeight(22), + width: getProportionateScreenWidth(22), + child: SvgPicture.asset( + pageProvider.currentIndex == index ? activeIcon : icon, + color: pageProvider.currentIndex == index + ? baruTextutih + : Theme.of(context).colorScheme.secondary, + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + Text( + title, + style: thirdTextStyle.copyWith( + color: pageProvider.currentIndex == index + ? baruTextutih + : Theme.of(context).colorScheme.secondary, + fontSize: getProportionateScreenWidth(10), + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + UsersInfo().getIdUser().then((value) { + print(value); + FirebaseMessaging.instance.subscribeToTopic("payment-before-paid-$value"); + FirebaseMessaging.instance.subscribeToTopic("payment-after-paid-$value"); + FirebaseMessaging.instance.subscribeToTopic("qna-new-qna-$value"); + FirebaseMessaging.instance.subscribeToTopic("qna-reply-qna-$value"); + FirebaseMessaging.instance.subscribeToTopic("alert-carts-$value"); + }); + UsersInfo().getToken().then((value) => print(value)); + PageProvider pageProvider = Provider.of<PageProvider>(context); + DateTime backButtonPressTime = DateTime.now(); + + SizeConfig().init(context); + Future<bool> handleWillPop(BuildContext context) async { + final now = DateTime.now(); + final backButtonHasNotBeenPressedOrSnackBarHasBeenClosed = + DateTime.now().difference(backButtonPressTime) >= + Duration(milliseconds: 500); + + if (backButtonHasNotBeenPressedOrSnackBarHasBeenClosed) { + backButtonPressTime = now; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + width: 240, + duration: Duration(seconds: 2), + backgroundColor: secondaryColor, + content: Text( + 'Tekan sekali lagi untuk keluar', + style: primaryTextStyle.copyWith( + color: backgroundColor, fontSize: 12), + textAlign: TextAlign.center, + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + ), + ); + return false; + } + + return true; + } + + Widget customBottomNavigation() { + return Container( + color: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + height: getProportionateScreenHeight(48), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + buildNavItem(0, 'assets/icons/featured.svg', + 'assets/icons/featured_click.svg', 'Home'), + buildNavItem(1, 'assets/icons/search.svg', + 'assets/icons/search_click.svg', 'Search'), + buildNavItem(2, 'assets/icons/my_course.svg', + 'assets/icons/my_course_click.svg', 'My Course'), + buildNavItem(3, 'assets/icons/wishlist.svg', + 'assets/icons/wishlist_click.svg', 'Wishlist'), + buildNavItem(4, 'assets/icons/profile.svg', + 'assets/icons/profile_click.svg', 'Profile'), + ], + ), + ); + } + + Widget body() { + switch (pageProvider.currentIndex) { + case 0: + return HomePage(); + case 1: + return SearchPage(); + case 2: + return MyCoursePage(); + case 3: + return WishlistPage(); + case 4: + return ProfilePage(); + default: + return HomePage(); + } + } + + return WillPopScope( + onWillPop: () => handleWillPop(context), + child: Scaffold( + backgroundColor: pageProvider.currentIndex == 1 + ? Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih + : Theme.of(context).brightness == Brightness.dark + ? backgroundColor + : baruTextutih, + body: body(), + bottomNavigationBar: customBottomNavigation(), + ), + ); + } +} diff --git a/lib/screens/login/components/get_user_data.dart b/lib/screens/login/components/get_user_data.dart new file mode 100644 index 0000000..534fa33 --- /dev/null +++ b/lib/screens/login/components/get_user_data.dart @@ -0,0 +1,16 @@ +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart'; +import 'package:provider/provider.dart'; + +getUserData(BuildContext context) async { + var email = await UsersInfo().getEmail(); + await Provider.of<UserInfoProvider>(context, listen: false) + .getUserInfo(email); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + await Provider.of<WishlistProvider>(context, listen: false).getWishlist(); + await Provider.of<MyCourseProvider>(context, listen: false).getMyCourse(); +} diff --git a/lib/screens/login/login_screen.dart b/lib/screens/login/login_screen.dart new file mode 100644 index 0000000..47fa664 --- /dev/null +++ b/lib/screens/login/login_screen.dart @@ -0,0 +1,239 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/login_provider.dart'; + +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/login_with_facebook/login_facebook_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/header.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:initial_folder/widgets/terms_and_privacy.dart'; +import 'package:provider/provider.dart'; +import 'login_with_email/login_email_screen.dart'; +import '../../../widgets/login_regist/default_button.dart'; +import '../../../widgets/login_regist/default_icon_button.dart'; +import 'package:flutter/gestures.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; + +import 'login_with_google/login_google_screen.dart'; + +class LoginScreen extends StatefulWidget { + static String routeName = "/login"; + + @override + State<LoginScreen> createState() => _LoginScreenState(); +} + +class _LoginScreenState extends State<LoginScreen> { + bool isLoadingGoogle = false; + bool isLoadingFacebook = false; + + @override + Widget build(BuildContext context) { + var loginProvider = Provider.of<LoginProvider>(context, listen: false); + imageCache!.clear(); + + return Scaffold( + body: Center(child: SafeArea( + child: SingleChildScrollView( + child: Consumer<LoginProvider>( + builder: (context, value, child) { + return Column(children: [ + Container( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(12), + top: getProportionateScreenHeight(8)), + alignment: Alignment.topLeft, + child: TextButton( + child: Text('Login nanti', + style: primaryTextStyle.copyWith( + color: primaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14))), + onPressed: () { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, + (Route<dynamic> route) => false); + print('nanti'); + })), + SizedBox(height: getProportionateScreenHeight(35)), + Container( + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Header( + jarak: 16, + text: + 'Masuk Untuk Melanjutkan Belajar dan Upgrade Skill Bersama Vocasia', + style: secondaryTextStyle.copyWith( + color: tenthColor, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(32)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15.0)), + child: value.loadGoogle + ? LoadingButton( + backgroundButtonColor: tenthColor, + textButtonColor: ninthColor) + : DefaultIconButton( + text: "Lanjutkan dengan Google", + press: () async { + loginProvider.loadGoogleActive(true); + await LoginGoogle() + .handleLoginGoogle(context); + }, + icon: 'assets/icons/google.png', + iconWidth: 25, + iconHeight: 25, + ), + ), + // SizedBox(height: getProportionateScreenWidth(16)), + // Padding( + // padding: EdgeInsets.symmetric( + // horizontal: getProportionateScreenWidth(15.0)), + // child: value.loadFacebook + // ? LoadingButton( + // backgroundButtonColor: tenthColor, + // textButtonColor: ninthColor) + // : DefaultIconButton( + // text: "Lanjutkan dengan Facebook", + // press: () { + // loginProvider.loadFacebookActive(true); + // LoginFacebook().handleLoginFacebook(context); + // }, + // icon: 'assets/icons/facebook.png', + // iconWidth: 25, + // iconHeight: 25, + // ), + // ), + SizedBox(height: getProportionateScreenWidth(16)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15.0)), + child: DefaultButton( + text: "Login dengan Email", + press: () { + //loginEmail = true; + Navigator.pushNamed(context, LoginEmail.routeName); + //print('Login Manual'); + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(32)), + Container( + // padding: EdgeInsets.only( + // left: getProportionateScreenWidth(5), + // top: getProportionateScreenWidth(5)), + alignment: Alignment.center, + child: RichText( + text: TextSpan( + children: <TextSpan>[ + TextSpan(text: 'Baru di Vocasia? '), + TextSpan( + text: 'Buat Akun', + style: primaryTextStyle.copyWith( + color: primaryColor), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.of(context) + .pushNamedAndRemoveUntil( + RegistrationScreen.routeName, + (Route<dynamic> route) => + false); + }), + ], + style: primaryTextStyle.copyWith( + color: tenthColor, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(14))))), + SizedBox(height: getProportionateScreenHeight(48)), + Container( + // padding: EdgeInsets.only( + // left: getProportionateScreenWidth(5), + // top: getProportionateScreenWidth(5)), + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(30)), + child: RichText( + textAlign: TextAlign.center, + maxLines: 2, + text: TextSpan( + children: <TextSpan>[ + TextSpan( + text: + 'Dengan membuat akun, anda menyetujui ', + ), + TextSpan( + text: 'S&K', + style: primaryTextStyle.copyWith( + color: primaryColor), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + TermsAndCondition( + url: + 'https://vocasia.id/home/terms_and_condition', + id: 'sk'), + ), + ); + }), + TextSpan(text: ' dan '), + TextSpan( + text: 'Kebijakan Privasi', + style: primaryTextStyle.copyWith( + color: primaryColor), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + TermsAndCondition( + url: + 'https://vocasia.id/home/privacy_policy', + id: 'prv'), + ), + ); + }), + ], + style: primaryTextStyle.copyWith( + height: 2, + color: tenthColor, + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(14))))), + SizedBox(height: getProportionateScreenHeight(90)), + ], + ), + ), + ]); + }, + ), + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 50), + // child: Text( + // 'Dengan membuat akun, anda menyetujui Kebijakan dan Persyaratan Privasi', + // maxLines: 2, + // textAlign: TextAlign.center, + // style: TextStyle( + // height: 2, + // fontSize: getProportionateScreenWidth(14), + // fontWeight: FontWeight.w400, + // color: Colors.white)), + // ), + ), + )), + ); + } +} diff --git a/lib/screens/login/login_with_email/login_email_screen.dart b/lib/screens/login/login_with_email/login_email_screen.dart new file mode 100644 index 0000000..0a8fe20 --- /dev/null +++ b/lib/screens/login/login_with_email/login_email_screen.dart @@ -0,0 +1,321 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/auth_provider.dart'; +import 'package:initial_folder/providers/login_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/components/get_user_data.dart'; +import 'package:initial_folder/screens/login/login_with_google/login_google_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/custom_text_form_field.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/default_icon_button.dart'; +import 'package:initial_folder/widgets/login_regist/failed_login.dart'; +import 'package:initial_folder/widgets/login_regist/footer.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import '../reset/reset_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; + +class LoginEmail extends StatefulWidget { + final _LoginEmailState loginEmailState = _LoginEmailState(); + + static String routeName = "/login_email"; + LoginEmail({Key? key}) : super(key: key); + + @override + _LoginEmailState createState() => loginEmailState; +} + +class _LoginEmailState extends State<LoginEmail> { + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + + final TextEditingController emailController = TextEditingController(text: ''); + + final TextEditingController passwordController = + TextEditingController(text: ''); + bool isLoading = false; + bool failed = false; + bool _isObscure = true; + @override + void dispose() { + emailController.dispose(); + passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var loginProvider = Provider.of<LoginProvider>(context, listen: false); + SizeConfig().init(context); + AuthProvider authProvider = Provider.of<AuthProvider>(context); + final themeProvider = Provider.of<ThemeProvider>(context,listen: false); + + handleSignIn() async { + setState(() { + isLoading = true; + }); + + if (await authProvider.login( + email: emailController.text, + password: passwordController.text, + )) { + Condition.loginEmail = true; + await UsersInfo().setEmail(emailController.text); + + await getUserData(context); + + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, (Route<dynamic> route) => false); + } else { + setState(() { + failed = true; + }); + } + + setState(() { + isLoading = false; + }); + } + + void _validateInputs() { + if (this._formKey.currentState!.validate()) { + handleSignIn(); + } + } + + Widget form() { + Widget emailInput() { + String? kondisiikonemail; + String? emailValidation = validateEmail(emailController.text); + if (emailController.text.isEmpty) { + kondisiikonemail = 'abu2'; + } else { + if (emailValidation == 'Email tidak boleh kosong' || + emailValidation == 'Mohon masukkan email yang valid') { + kondisiikonemail = 'merah'; + } else { + kondisiikonemail = 'ijo'; + } + } + emailController.addListener(() { + if (emailController.text.isEmpty) { + kondisiikonemail = 'abu2'; + } else { + if (emailValidation == 'Email tidak boleh kosong' || + emailValidation == 'Mohon masukkan email yang valid') { + kondisiikonemail = 'merah'; + } else { + kondisiikonemail = 'ijo'; + } + } + setState(() {}); + }); + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: emailController, + hinttext: 'Inputyouremail@gmail.com', + title: 'Email', + validate: validateEmail, + textInputAction: TextInputAction.next, + suffix: Icon( + FeatherIcons.checkCircle, + color: kondisiikonemail == 'ijo' + ? Colors.green + : kondisiikonemail == 'merah' + ? Colors.red + : Colors.grey, + size: 18, + ), + ); + } + + Widget passwordInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: passwordController, + hinttext: 'Input your password', + title: 'Password', + obscuretext: _isObscure, + validate: validatePassword, + suffix: GestureDetector( + onTap: () => setState(() { + _isObscure = !_isObscure; + }), + child: _isObscure + ? Icon( + FeatherIcons.eyeOff, + color: secondaryColor, + size: 18, + ) + : Icon( + FeatherIcons.eye, + color: secondaryColor, + size: 18, + ), + ), + textInputAction: TextInputAction.done, + ); + } + + Widget forgotPassword() { + MediaQuery.of(context).platformBrightness; + + return Container( + child: Column( + children: [ + Container( + padding: EdgeInsets.only(right: getProportionateScreenWidth(12)), + alignment: AlignmentDirectional.topEnd, + child: GestureDetector( + child: Text( + 'Lupa password?', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + ), + ), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ResetScreen( + email: emailController.text, + ), + ), + ); + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(35)), + ], + )); + } + + Widget button() { + return Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(60)), + child: isLoading + ? LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: Color(0xff050505)) + : DefaultButton( + text: 'Log In', + press: _validateInputs, + ), + ); + } + + return Form( + key: _formKey, + child: Column( + children: [emailInput(), passwordInput(), forgotPassword(), button()], + ), + ); + } + + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(getProportionateScreenWidth(25)), + child: Align( + alignment: Alignment.center, + child: Text('Log In', + style: thirdTextStyle.copyWith( + fontWeight: FontWeight.w900, + fontSize: getProportionateScreenWidth(20), + letterSpacing: 0.5))), + ), + Container( + child: Align( + alignment: Alignment.center, + child: failed + ? FailedLogin( + style: primaryTextStyle.copyWith( + color: sevenColor, + ), + text: + 'Terdapat kendala dalam login, periksa kembali email dan password anda', + ) + : SizedBox()), + ), + SizedBox( + height: getProportionateScreenHeight(32), + ), + form(), + SizedBox(height: getProportionateScreenHeight(10)), + Footer( + textOne: 'Belum punya akun? ', + textTwo: 'Register', + route: RegistrationEmail.routeName, + ), + Consumer<LoginProvider>( + builder: (context, value, child) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(30.0)), + child: value.loadGoogle + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 17, + height: 17, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ), + ), + SizedBox( + width: getProportionateScreenWidth(6), + ), + Text( + 'Loading', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + fontWeight: semiBold, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode , + letterSpacing: 0.3), + ), + ], + ) + : + // fitur login googgle ini bisa bekerja hanya ketika sha 1 dan sha 256 laptop mu sudah di daftarkan- + // di firebase vocasia, caranya buka terminal vscode, masuk ke folder android dengan- + // cara cd android, setelah itu jalankan ./gradlew signingReport nanti muncul di situ sha nya- + // setelah muncul, berikan sha key nya ke bang sergi atau bang rizky untuk di daftarkan- + // di fingerprint firebase vocasia + DefaultIconButton( + text: "Continue With Google", + press: () async { + loginProvider.loadGoogleActive(true); + // baca comment di atas + await LoginGoogle().handleLoginGoogle(context); + }, + icon: 'assets/icons/google.png', + iconWidth: 25, + iconHeight: 25, + ), + ); + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/login/login_with_facebook/login_facebook_screen.dart b/lib/screens/login/login_with_facebook/login_facebook_screen.dart new file mode 100644 index 0000000..2dcec8d --- /dev/null +++ b/lib/screens/login/login_with_facebook/login_facebook_screen.dart @@ -0,0 +1,76 @@ +// import 'package:flutter/material.dart'; +// import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +// import 'package:initial_folder/providers/incomplete_profile_provider.dart'; +// import 'package:initial_folder/providers/login_provider.dart'; +// import 'package:initial_folder/screens/home/home_screen.dart'; +// import 'package:initial_folder/screens/login/components/get_user_data.dart'; +// import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +// import 'package:initial_folder/screens/profile/account_sign_in/incomplete_profile_screen.dart'; +// import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +// import 'package:provider/provider.dart'; + +// import '../../../size_config.dart'; +// import '../../../theme.dart'; + +// class LoginFacebook { +// Future handleLoginFacebook(BuildContext context) async { +// final provider = +// Provider.of<FirebaseAuthenticationProvider>(context, listen: false); +// if (await provider.facebookLogin()) { +// await getUserData(context); + +// Condition.loginFirebase = true; + +// IncompleteProfileProvider userInfoIncomplete = +// Provider.of<IncompleteProfileProvider>(context, listen: false); +// await userInfoIncomplete.getUserInfoIncomplete(); +// if (userInfoIncomplete.isUserInfoComplete == null) { +// Navigator.of(context).pushNamedAndRemoveUntil( +// LoginEmail.routeName, (Route<dynamic> route) => false); +// return; +// } + +// if (userInfoIncomplete.isUserInfoComplete!) { +// Navigator.of(context).pushNamedAndRemoveUntil( +// HomeScreen.routeName, (Route<dynamic> route) => false); +// } else { +// Navigator.of(context).pushNamedAndRemoveUntil( +// IncompleteProfile.routeName, (Route<dynamic> route) => false); +// } +// } else { +// showDialog( +// context: context, +// builder: (context) => AlertDialog( +// insetPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 20), +// content: Container( +// alignment: AlignmentDirectional.bottomCenter, +// height: 50, +// child: Text( +// 'Ada kendala dalam login, coba lagi atau periksa akun anda', +// style: primaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(12), letterSpacing: 1), +// ), +// ), +// actions: [ +// Container( +// height: 30, +// alignment: AlignmentDirectional.topEnd, +// child: GestureDetector( +// onTap: () { +// Navigator.of(context).pop(); +// }, +// child: Text('Tutup', +// style: primaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(12), +// letterSpacing: 1, +// color: primaryColor)), +// ), +// ), +// ], +// ), +// ); +// } +// Provider.of<LoginProvider>(context, listen: false) +// .loadFacebookActive(false); +// } +// } diff --git a/lib/screens/login/login_with_google/login_google_screen.dart b/lib/screens/login/login_with_google/login_google_screen.dart new file mode 100644 index 0000000..d8567b3 --- /dev/null +++ b/lib/screens/login/login_with_google/login_google_screen.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +import 'package:initial_folder/providers/incomplete_profile_provider.dart'; +import 'package:initial_folder/providers/login_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/components/get_user_data.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/incomplete_profile_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:provider/provider.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class LoginGoogle { + Future handleLoginGoogle(BuildContext context) async { + bool googleLogged = false; + bool isAccountNotRegistered = false; + final provider = + Provider.of<FirebaseAuthenticationProvider>(context, listen: false); + final themeProvider = Provider.of<ThemeProvider>(context, listen: false); + + try { + googleLogged = await provider.googleLogin(); + } catch (e) { + if (e.toString() == 'Exception: Akun anda belum terdaftar') { + isAccountNotRegistered = true; + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.background, + surfaceTintColor: Colors.transparent, + insetPadding: + const EdgeInsets.symmetric(horizontal: 25, vertical: 20), + content: Container( + margin: EdgeInsets.only(top: 10, left: 15, right: 15), + height: 70, + alignment: AlignmentDirectional.topStart, + child: Text( + 'Akun anda belum terdaftar, silahkan daftar terlebih dahulu', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).colorScheme.onBackground, + ), + ), + ), + actions: [ + Container( + margin: EdgeInsets.only(right: 25), + alignment: AlignmentDirectional.topEnd, + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => RegistrationEmail(), + ), + ); + }, + child: Text( + 'Daftar', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor, + ), + ), + ), + ), + ], + ), + ); + } else { + print('Error: ${e.toString()}'); + } + Provider.of<LoginProvider>(context, listen: false) + .loadGoogleActive(false); + } + + if (googleLogged) { + await getUserData(context); + + Condition.loginFirebase = true; + + IncompleteProfileProvider userInfoIncomplete = + Provider.of<IncompleteProfileProvider>(context, listen: false); + + // [!CHECKPOINT!]: + await userInfoIncomplete.getUserInfoIncomplete(); + + // [!CHECKPOINT!]: + if (userInfoIncomplete.isUserInfoComplete == null) { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + return; + } + + if (userInfoIncomplete.isUserInfoComplete!) { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, (Route<dynamic> route) => false); + } else { + Navigator.of(context).pushNamedAndRemoveUntil( + IncompleteProfile.routeName, (Route<dynamic> route) => false); + } + } else if (!isAccountNotRegistered) { + void fn() => showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context).colorScheme.background, + surfaceTintColor: Colors.transparent, + insetPadding: + const EdgeInsets.symmetric(horizontal: 25, vertical: 20), + content: Container( + margin: EdgeInsets.only(top: 10, left: 15, right: 15), + height: 70, + alignment: AlignmentDirectional.topStart, + child: Text( + 'Ada kendala dalam login, coba lagi atau periksa akun anda', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).colorScheme.onBackground, + ), + ), + ), + actions: [ + Container( + margin: EdgeInsets.only(right: 25), + alignment: AlignmentDirectional.topEnd, + child: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Text( + 'Tutup', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ), + ), + ), + ], + ), + ); + fn(); + } + Provider.of<LoginProvider>(context, listen: false).loadGoogleActive(false); + } +} diff --git a/lib/screens/login/reset/reset_screen.dart b/lib/screens/login/reset/reset_screen.dart new file mode 100644 index 0000000..8dd3b28 --- /dev/null +++ b/lib/screens/login/reset/reset_screen.dart @@ -0,0 +1,193 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/forgot_password_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import 'success_screen.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:flutter/src/material/scaffold.dart'; +import 'package:initial_folder/providers/reset_provider.dart'; +import 'package:initial_folder/widgets/login_regist/custom_text_form_field.dart'; + +class ResetScreen extends StatefulWidget { + static String routeName = "/reset"; + ResetScreen({Key? key, this.email = ''}) : super(key: key); + final String email; + + + @override + State<ResetScreen> createState() => _ResetScreenState(); +} + +class _ResetScreenState extends State<ResetScreen> { + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + final TextEditingController emailController = TextEditingController(); + bool isLoading = false; + + + void showRePaEr(Map<String, dynamic> error) { + showDialog( + context: context, + builder: (context) => AlertDialog( + surfaceTintColor: Colors.transparent, + insetPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 20), + content: Container( + alignment: AlignmentDirectional.bottomCenter, + height: 50, + child: Text( + // error['messages']['error'] ?? '', + "Terjadi kesalahan, gagal reset password", + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + ), + actions: [ + Container( + height: 30, + alignment: AlignmentDirectional.topEnd, + child: GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Tutup', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + final _resetProvider = Provider.of<ResetProvider>(context, listen: false); + + Future<void> _resetPassword() async { + try { + bool success = await _resetProvider.resetPassword(email: emailController.text); + if (success) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ResetSuccess(email: emailController.text), + ), + ); + } else { + showRePaEr({"message": "Gagal reset password, coba lagi."}); + } + } catch (e) { + print(e); + showRePaEr({"message": "Terjadi kesalahan saat reset password."}); + } + setState(() { + isLoading = false; + }); + } + SizeConfig().init(context); + + Widget form() { + Widget resetEmailInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: emailController, + hinttext: 'Masukkan email', + title: 'Email', + validate: validateEmail, + ); + } + + Widget button() { + if (isLoading) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(60), + ), + child: const LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: ninthColor, + ), + ); + } else { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(60)), + child: DefaultButton( + text: 'Reset password', + press: () { + if (_formKey.currentState!.validate()) { + setState(() { + isLoading = true; + }); + _resetPassword(); + } + }, + ), + ); + } + } + + return Form( + key: _formKey, + child: Column( + children: [ + resetEmailInput(), + button(), + ], + ), + ); + } + + return SafeArea( + child: Scaffold( + resizeToAvoidBottomInset: false, + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Reset Password', + style: thirdTextStyle.copyWith( + fontWeight: bold, + ), + ), + ), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(15), + ), + Container( + child: Padding( + padding: EdgeInsets.all(getProportionateScreenWidth(12)), + child: Align( + alignment: Alignment.center, + child: Text( + 'Masukkan email yang terhubung ke akunmu dan kami akan mengirimkan link untuk mereset password', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + textAlign: TextAlign.justify, + )), + )), + SizedBox( + height: getProportionateScreenHeight(32), + ), + form(), + SizedBox(height: getProportionateScreenHeight(32)), + SizedBox(height: getProportionateScreenHeight(32)), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/login/reset/success_screen.dart b/lib/screens/login/reset/success_screen.dart new file mode 100644 index 0000000..dbee12c --- /dev/null +++ b/lib/screens/login/reset/success_screen.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/gestures.dart'; +import 'package:initial_folder/screens/login/reset/reset_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/header.dart'; +import 'package:initial_folder/services/reset_service.dart'; + +class ResetSuccess extends StatelessWidget { + static String routeName = "/reset_success"; + final String email; + + ResetSuccess({Key? key, this.email = ''}) : super(key: key); + + final TextEditingController nameController = TextEditingController(text: ''); + final TextEditingController emailController = TextEditingController(text: ''); + final TextEditingController passwordController = TextEditingController(text: ''); + + Future<void> resendEmail(BuildContext context) async { + try { + await ResetService().kirimEmail(email: email); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Link reset berhasil dikirim ulang ke email Anda')), + ); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Gagal mengirim ulang email. Silakan coba lagi.')), + ); + } + } + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = MediaQuery.of(context).platformBrightness; + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + SizeConfig().init(context); + + return SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + title: Text( + 'Reset Password', + style: thirdTextStyle.copyWith( + color: isDarkMode ? baruTextutih : baruTexthitam, + fontWeight: bold), + ), + ), + body: Center( + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon( + Icons.check_rounded, + color: Colors.greenAccent[700], + size: getProportionateScreenWidth(60), + ), + SizedBox(height: getProportionateScreenHeight(24)), + Header( + title: false, + text2: "Sukses", + text: 'Cek emailmu untuk lanjut mereset password', + style: primaryTextStyle.copyWith( + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ), + ), + SizedBox(height: getProportionateScreenHeight(44)), + Container( + alignment: Alignment.center, + child: RichText( + text: TextSpan( + children: <TextSpan>[ + TextSpan( + text: 'Kirim Ulang ', + style: thirdTextStyle.copyWith( + color: isDarkMode ? + primaryColor : primaryColorligtmode, + fontWeight: bold, + fontSize: getProportionateScreenWidth(12), + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + resendEmail(context); // Panggil fungsi kirim ulang + }, + ), + TextSpan( + text: "atau ", + style: thirdTextStyle.copyWith( + color: isDarkMode ? baruTextutih : fifteenColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + ), + ), + TextSpan( + text: 'Masuk', + style: thirdTextStyle.copyWith( + color: isDarkMode ?primaryColor : primaryColorligtmode, + fontWeight: bold, + fontSize: getProportionateScreenWidth(12), + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.of(context).pop(); // Kembali ke layar sebelumnya + Navigator.of(context).pop(); // Tutup reset_screen + }, + ), + ], + style: primaryTextStyle.copyWith( + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + ), + ]), + ), + ), + ); + } +} diff --git a/lib/screens/my_course/success_free_course.dart b/lib/screens/my_course/success_free_course.dart new file mode 100644 index 0000000..44356d4 --- /dev/null +++ b/lib/screens/my_course/success_free_course.dart @@ -0,0 +1,183 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/instructor_provider.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart' + as myCourseProvider; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/screens/home/components/body_comp/latest_course.dart'; +import 'package:initial_folder/screens/home/components/home_page.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/instructor_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +// import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/default_button_payment.dart'; +import 'package:provider/provider.dart'; + +class SuccessFreeCourse extends StatelessWidget { + const SuccessFreeCourse({ + Key? key, + this.title, + this.id, + this.thumbnail, + this.instructor, + }) : super(key: key); + + final String? instructor, id, thumbnail, title; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + elevation: 0.0, + // leadingWidth: 14, + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () async { + // print(id); + // // await Provider.of<DetailCourseProvider>(context, listen: false) + // // .getDetailCourseLogin(id); + // await Provider.of<myCourseProvider.MyCourseProvider>(context, + // listen: false) + // .getMyCourse(); + // Navigator.pop(context, true); // Mengirim true sebagai nilai balik + // // print('object'); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => HomeScreen()), + (Route<dynamic> route) => false, + ); + }, + ), + ), + body: ListView( + children: [ + SizedBox(height: getProportionateScreenHeight(8)), + Image.asset( + "assets/images/success_pay.png", + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(100), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Text( + 'Pembayaran Berhasil', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + textAlign: TextAlign.center, + ), + SizedBox(height: getProportionateScreenHeight(15)), + Container( + margin: EdgeInsets.only(left: 16, right: 16), + width: SizeConfig.screenWidth, + height: getProportionateScreenHeight(169), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + ), + child: Column( + children: [ + Container( + width: getProportionateScreenWidth(278), + height: getProportionateScreenHeight(145), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + image: NetworkImage(thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg'), + fit: BoxFit.cover), + ), + ), + ], + ), + ), + Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(8), + ), + child: Center( + child: Text( + title ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.2, + ), + textAlign: TextAlign.center, + ), + ), + ), + Container( + child: Center( + child: Text( + 'Oleh $instructor ', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + letterSpacing: 0.5, + fontFamily: "Poppins", + ), + textAlign: TextAlign.center, + ), + ), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + Container( + margin: EdgeInsets.only(left: 16, right: 16), + child: DefaultButtonPayment( + width: double.infinity, + height: 35, + text: 'Mulai Kursus', + press: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => LessonCourseProvider( + lessonCourseService: LessonCourseService(), + id: int.parse(id ?? '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), id: id ?? '1')), + ChangeNotifierProvider( + create: (context) => InstructorProvider( + instructorService: InstructorService(), + id: int.parse(instructor!)), + ), + ChangeNotifierProvider( + create: (context) => + MyCourseProvider(courseService: CourseService()), + ), + ], + child: PlayCourse( + judul: title ?? '', + instruktur: instructor ?? '', + thumbnail: thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + courseeid: id ?? '', + ), + ), + ), + ); + }, + ), + ), + SizedBox( + height: getProportionateScreenHeight(20), + ), + LatestCourse(text: "Kursus Terbaru"), + ], + ), + ); + } +} diff --git a/lib/screens/profile/account_not_sign_in/not_sign_in_screen.dart b/lib/screens/profile/account_not_sign_in/not_sign_in_screen.dart new file mode 100644 index 0000000..cd0d6cf --- /dev/null +++ b/lib/screens/profile/account_not_sign_in/not_sign_in_screen.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/screens/profile/components/about_profile_list.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/terms_and_privacy.dart'; +import 'package:provider/provider.dart'; + +class NotSignInScreen extends StatefulWidget { + const NotSignInScreen({Key? key}) : super(key: key); + + @override + State<NotSignInScreen> createState() => _NotSignInScreenState(); +} + +class _NotSignInScreenState extends State<NotSignInScreen> { + final double maximumRadius = 1000; + + @override + Widget build(BuildContext context) { + PageProvider pageProvider = + Provider.of<PageProvider>(context, listen: false); + final themeProvider = Provider.of<ThemeProvider>(context); + + return SafeArea( + child: Scaffold( + backgroundColor: Colors.transparent, + body: Container( + margin: EdgeInsets.all(getProportionateScreenWidth(16)), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Akun', + style: secondaryTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 2.3, + fontSize: getProportionateScreenWidth(16)), + ), + IconButton( + icon: SvgPicture.asset( + themeProvider.themeData == ThemeClass.darkmode + ? 'assets/icons/moon.svg' + : 'assets/icons/sun.svg', + ), + onPressed: () { + setState(() { + themeProvider.toggleTheme(); + }); + }, + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(15)), + AboutAccountList( + onPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TermsAndCondition( + url: + 'https://vocasia-v4-develop.vercel.app/about-us', + id: 'about'), + ), + ); + }, + title: 'Tentang Vocasia'), + AboutAccountList( + onPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TermsAndCondition( + url: + 'https://vocasia-v4-develop.vercel.app/syarat-ketentuan', + id: 'sk'), + ), + ); + }, + title: 'Syarat dan Ketentuan'), + AboutAccountList( + onPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TermsAndCondition( + url: + 'https://vocasia-v4-develop.vercel.app/privacy-policy', + id: 'prv'), + ), + ); + }, + title: 'Kebijakan Privasi'), + AboutAccountList( + onPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TermsAndCondition( + url: + 'https://vocasia-v4-develop.vercel.app/contact', + id: 'ctc'), + ), + ); + }, + title: 'Kontak Kami'), + AboutAccountList( + onPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TermsAndCondition( + url: + 'https://vocasia-v4-develop.vercel.app/help', + id: 'help'), + ), + ); + }, + title: 'Bantuan'), + SizedBox(height: getProportionateScreenHeight(15)), + Center( + child: DefaultButton( + text: 'Masuk', + press: () { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, + (Route<dynamic> route) => false); + pageProvider.remove(); + }, + ), + ) + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/data_diri.dart b/lib/screens/profile/account_sign_in/data_diri.dart new file mode 100644 index 0000000..0929586 --- /dev/null +++ b/lib/screens/profile/account_sign_in/data_diri.dart @@ -0,0 +1,874 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/profile_image_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/providers/update_data_diri_provider.dart'; +import 'package:initial_folder/providers/data_diri_provider.dart'; +import 'package:initial_folder/services/user_info_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/widgets/login_regist/custom_profile_text_field.dart'; +import 'package:initial_folder/providers/user_info_provider.dart' as userInfo; +import 'package:provider/provider.dart'; + +class DataDiri extends StatefulWidget { + @override + State<DataDiri> createState() => _DataDiriState(); +} + +class _DataDiriState extends State<DataDiri> { + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + + final List<String> gender = ['pria', 'wanita']; + void _handleGenderChange(String? value, DataDiriProvider state) { + setState(() { + newGender = value ?? ''; + state.newGender = true; + isGenderEmpty = false; + }); + } + + late String newGender = ''; + late String newName; + late String newBirthDate = ''; + late String newHeadline; + late String newBiograpy; + late String newEmail; + late String newPhone; + late String newInstagram = ''; + late String newTwitter = ''; + late String newFacebook = ''; + late String newLinkedin = ''; + + bool? isNameEmpty; + bool? isGenderEmpty; + bool? isBirthDateEmpty; + bool? isEmailEmpty; + bool? isPhoneEmpty; + + DateTime birthDate = DateTime.now(); + + bool isLoading = false; + + String? email = ""; + + void getUserEmail() async { + email = await UsersInfo().getEmail(); + } + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + UpdateDataDiriProvider updateDataDiriProvider = + Provider.of<UpdateDataDiriProvider>(context); + + ProfileImageProvider profileImageProvider = + Provider.of<ProfileImageProvider>(context, listen: false); + final ImagePicker _picker = ImagePicker(); + + Future _showMessage(String text) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + backgroundColor: Colors.white, + surfaceTintColor: Colors.transparent, + contentPadding: EdgeInsets.fromLTRB(22, 30, 22, 30), + content: Text( + text, + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: baruTexthitam, + ), + ), + ), + ); + } + + void takePhoto(ImageSource source) async { + try { + final XFile? pickedFile = await _picker.pickImage(source: source); + var imageFile = (pickedFile != null) ? File(pickedFile.path) : File(''); + var email = await UsersInfo().getEmail(); + if (await profileImageProvider.addProfileImage(pckFile: imageFile)) { + setState(() { + isLoading = true; + }); + await Future.delayed(Duration(seconds: 2)); + profileImageProvider.setImageFile(imageFile); + setState(() { + imageFile; + isLoading = false; + }); + // await Provider.of<userInfo.UserInfoProvider>(context, listen: false) + // .getUserInfo(email); + + _showMessage('Berhasil Upload Image'); + } + } on PlatformException catch (e) { + print('Failed to pick image : $e'); + } + } + + Widget bottomSheet() { + return Container( + height: 100.0, + width: MediaQuery.of(context).size.width, + margin: EdgeInsets.symmetric( + horizontal: 20, + vertical: 20, + ), + child: Column( + children: <Widget>[ + Text( + "Choose Profile photo", + style: secondaryTextStyle.copyWith(fontSize: 20), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton.icon( + icon: Icon(Icons.camera), + onPressed: () { + Navigator.pop(context); + takePhoto(ImageSource.camera); + }, + label: Text( + "Camera", + style: primaryTextStyle, + ), + ), + TextButton.icon( + icon: Icon(Icons.image), + onPressed: () { + Navigator.pop(context); + takePhoto(ImageSource.gallery); + }, + label: Text("Gallery", style: primaryTextStyle), + ), + ], + ), + ], + ), + ); + } + + Widget imageProfile(String? urlImage) { + return Stack( + alignment: AlignmentDirectional.center, + children: [ + CircleAvatar( + radius: getProportionateScreenWidth(40), + backgroundColor: Colors.amber, + backgroundImage: profileImageProvider.imageFile != null + ? FileImage(profileImageProvider.imageFile!) as ImageProvider + : urlImage != null + ? NetworkImage(urlImage) as ImageProvider + : AssetImage("assets/images/Profile Image.png") + as ImageProvider, + ), + CircleAvatar( + radius: getProportionateScreenWidth(40), + backgroundColor: Color(0xff000000).withOpacity(0.5), + ), + IconButton( + onPressed: () { + showModalBottomSheet( + context: context, + builder: (context) => GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + }, + child: bottomSheet(), + ), + ); + }, + icon: Icon( + Icons.camera_alt_outlined, + color: Colors.white, + ), + ) + ], + ); + } + + return SafeArea( + child: ChangeNotifierProvider( + create: (context) => + DataDiriProvider(userInfoService: UserInfoService()), + child: Consumer<DataDiriProvider>( + builder: (context, state, _) { + if (state.state == ResultStateData.Loading) { + return Scaffold( + body: Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ), + ); + } else if (state.state == ResultStateData.HasData) { + var result = state.result!.data[0]; + newGender = state.newGender ? newGender : result.gender ?? ''; + return Scaffold( + appBar: AppBar( + elevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Data Diri', + style: thirdTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: bold, + fontSize: getProportionateScreenWidth(14)), + ), + actions: [ + TextButton( + child: isLoading + ? CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ) + : Text('Simpan', + style: thirdTextStyle.copyWith( + color:themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontWeight: reguler, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12))), + onPressed: () async { + setState(() { + isLoading = true; + }); + if (this._formKey.currentState!.validate()) { + if (await updateDataDiriProvider.dataDiriUpdate( + fullname: + state.newName ? newName : result.fullname, + headline: state.newHeadline + ? newHeadline + : result.headline, + biograph: state.newBiograpy + ? newBiograpy + : result.biography, + twitter: state.newTwitter + ? newTwitter + : result.socialLink?.twitter, + facebook: state.newFacebook + ? newFacebook + : result.socialLink?.facebook, + linkedin: state.newLinkedin + ? newLinkedin + : result.socialLink?.linkedin, + instagram: state.newInstagram + ? newInstagram + : result.socialLink?.instagram, + phone: state.newPhone ? newPhone : result.phone, + // datebirth: state.isNewBirthDate + // ? newBirthDate + // : result.datebirth, + // gender: + // state.newGender ? newGender : result.gender, + email: + state.newEmail ? newEmail : result.email)) { + await Provider.of<DataDiriProvider>(context, + listen: false) + .getDataDiri(); + _showMessage('Data diri berhasil diubah'); + } else { + if (updateDataDiriProvider.updateDataDiriModel != + null) { + dynamic updateDataDiriMessages = + updateDataDiriProvider + .updateDataDiriModel!.messages; + if (updateDataDiriMessages is! String) { + if (updateDataDiriMessages.fullname + is String) { + setState(() { + isNameEmpty = true; + }); + } + + if (updateDataDiriMessages.gender is String) { + setState(() { + isGenderEmpty = true; + }); + } + + if (updateDataDiriMessages.datebirth + is String) { + setState(() { + isBirthDateEmpty = true; + }); + } + + if (updateDataDiriMessages.phone is String) { + setState(() { + isPhoneEmpty = true; + }); + } + + if (updateDataDiriMessages.email is String) { + setState(() { + isEmailEmpty = true; + }); + } + } + } else { + _showMessage('Silahkan Coba lagi'); + } + + if (state.newGender) { + if (newGender.isEmpty) { + setState(() { + isGenderEmpty = true; + }); + } + } else { + if (result.gender == null) { + setState(() { + isGenderEmpty = true; + }); + } + } + + if (state.isNewBirthDate) { + if (newBirthDate.isEmpty) { + setState(() { + isBirthDateEmpty = true; + }); + } + } else { + if (result.datebirth == null) { + setState(() { + isBirthDateEmpty = true; + }); + } + } + } + } + setState(() { + isLoading = false; + }); + }) + ], + ), + body: ListView( + children: [ + Container( + height: getProportionateScreenHeight(100), + // width: 360, + decoration: BoxDecoration( + color: Colors.grey[300], + image: DecorationImage( + image: AssetImage( isDarkMode + ? 'assets/images/cover_dark.png' + : 'assets/images/cover_light.png'), + fit: BoxFit.cover, + ), + ), + // child: const Padding( + // padding: EdgeInsets.all(10), + // child: Align( + // alignment: Alignment.bottomRight, + // // child: Icon( + // // Icons.camera_alt, + // // color: Colors.white, + // // ), + // ), + // ), + ), + Transform.translate( + offset: Offset(0, getProportionateScreenHeight(-45)), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Consumer<userInfo.UserInfoProvider>( + builder: (context, state, _) { + if (state.state == + userInfo.ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == + userInfo.ResultState.HasData) { + return Center( + child: imageProfile( + state.result?.data[0].fotoProfile), + ); + } else if (state.state == + userInfo.ResultState.NoData) { + return Center(child: Text(state.message)); + } else { + return Center(child: Text('')); + } + }), + ), + SizedBox(height: getProportionateScreenHeight(16)), + Form( + key: _formKey, + child: Column( + children: [ + CustomProfileTextField( + pad: getProportionateScreenWidth(2), + text: state.newName + ? newName + : result.fullname ?? '', + hinttext: 'Tuliskan nama lengkap Anda', + title: 'Nama Lengkap*', + validate: validateName, + onChanged: (value) { + state.newName = true; + newName = value; + }, + isErrorManual: isNameEmpty ?? false, + textErrorManual: + "Nama lengkap tidak boleh kosong", + ), + CustomProfileTextField( + pad: getProportionateScreenWidth(2), + text: state.newHeadline + ? newHeadline + : result.headline ?? '', + hinttext: 'Maksimal 60 kalimat', + title: 'Headline', + onChanged: (value) { + state.newHeadline = true; + newHeadline = value; + }, + ), + CustomProfileTextField( + pad: getProportionateScreenWidth(2), + text: state.newBiograpy + ? newBiograpy + : result.biography ?? '', + hinttext: 'Tulis biografi singkat kamu', + title: 'Biografi', + minLines: 1, + maxLines: 5, + onChanged: (value) { + state.newBiograpy = true; + newBiograpy = value; + }, + ), + // Column( + // crossAxisAlignment: + // CrossAxisAlignment.start, + // children: [ + // Text( + // 'Jenis Kelamin', + // style: thirdTextStyle.copyWith( + // fontWeight: semiBold, + // fontSize: + // getProportionateScreenWidth( + // 12), + // color: isDarkMode + // ? baruTextutih + // : baruTexthitam, + // letterSpacing: 0.5), + // ), + // Row( + // mainAxisAlignment: + // MainAxisAlignment.start, + // children: [ + // Radio<String>( + // value: 'pria', + // groupValue: newGender, + // onChanged: (value) { + // _handleGenderChange( + // value, state); + // }, + // ), + // Text( + // 'Laki-Laki', + // style: thirdTextStyle.copyWith( + // fontSize: + // getProportionateScreenWidth( + // 12), + // color: isDarkMode + // ? baruTextutih + // : baruTexthitam, + // letterSpacing: 0.5), + // ), + // Radio<String>( + // value: 'wanita', + // groupValue: newGender, + // onChanged: (value) { + // _handleGenderChange( + // value, state); + // }, + // ), + // Text( + // 'Perempuan', + // style: thirdTextStyle.copyWith( + // fontSize: + // getProportionateScreenWidth( + // 12), + // color: isDarkMode + // ? baruTextutih + // : baruTexthitam, + // letterSpacing: 0.5), + // ), + // ], + // ), + // customTextValidator(isGenderEmpty, + // 'Jenis Kelamin tidak boleh kosong') + // ], + // ), + // SizedBox(height: 15), + // GestureDetector( + // child: Container( + // margin: EdgeInsets.only( + // bottom: + // getProportionateScreenWidth(20), + // ), + // child: Column( + // crossAxisAlignment: + // CrossAxisAlignment.start, + // children: [ + // Text('Tanggal Lahir', + // style: secondaryTextStyle + // .copyWith( + // fontWeight: semiBold, + // fontSize: + // getProportionateScreenWidth( + // 12), + // color: isDarkMode + // ? baruTextutih + // : baruTexthitam, + // letterSpacing: 0.5)), + // SizedBox( + // height: + // getProportionateScreenWidth( + // 5), + // ), + // Container( + // width: double.infinity, + // height: + // getProportionateScreenWidth( + // 50), + // margin: + // EdgeInsets.only(bottom: 7), + // child: Row( + // mainAxisAlignment: + // MainAxisAlignment + // .spaceBetween, + // children: [ + // Container( + // margin: EdgeInsets.only( + // left: 13), + // child: Text( + // state.isNewBirthDate + // ? newBirthDate + // : result.datebirth ?? + // 'YYYY-MM-DD', + // style: primaryTextStyle + // .copyWith( + // fontSize: + // getProportionateScreenWidth( + // 12), + // color: state + // .isNewBirthDate + // ? baruTexthitam + // : secondaryColor, + // letterSpacing: 0.5, + // ), + // ), + // ), + // Padding( + // padding: EdgeInsets.only( + // right: + // getProportionateScreenWidth( + // 5)), + // child: Icon(Icons + // .keyboard_arrow_down_outlined), + // ) + // ], + // ), + // decoration: BoxDecoration( + // color: isDarkMode + // ? Color(0xff212643) + // : Color(0xffF2F2F2), + // borderRadius: + // BorderRadius.circular(10), + // border: Border.all( + // color: isBirthDateEmpty == + // null + // ? isDarkMode + // ? Color(0xff212643) + // : Color(0xffF2F2F2) + // : isBirthDateEmpty! + // ? Colors.red[700] ?? + // Color( + // 0xffd32f2f) + // : isDarkMode + // ? Color( + // 0xff212643) + // : Color( + // 0xffF2F2F2), + // ), + // ), + // ), + // customTextValidator( + // isBirthDateEmpty, + // 'Tanggal lahir tidak boleh kosong'), + // ], + // ), + // ), + // onTap: () async { + // DateTime initialDate = state + // .isNewBirthDate + // ? DateTime( + // int.parse(newBirthDate + // .split('-')[0]), + // int.parse(newBirthDate + // .split('-')[1]), + // int.parse(newBirthDate + // .split('-')[2])) + // : result.datebirth == null + // ? DateTime.now() + // : DateTime( + // int.parse(result + // .datebirth! + // .split('-')[0]), + // int.parse(result + // .datebirth! + // .split('-')[1]), + // int.parse( + // result.datebirth!.split('-')[2])); + // final datePick = await showDatePicker( + // context: context, + // initialDate: initialDate, + // firstDate: DateTime(1900), + // lastDate: DateTime(2100), + // builder: (BuildContext context, + // Widget? child) { + // return Theme( + // data: + // Theme.of(context).copyWith( + // datePickerTheme: + // DatePickerThemeData( + // backgroundColor: + // Colors.white, + // headerBackgroundColor: + // primaryColor, + // headerForegroundColor: + // Colors.white, + // surfaceTintColor: + // Colors + // .transparent), + // colorScheme: + // ColorScheme.light( + // primary: + // primaryColor, // header background color + // onPrimary: Colors + // .white, // header text color + // onSurface: + // primaryColor, // body text color + // ), + // textButtonTheme: + // TextButtonThemeData( + // style: TextButton.styleFrom( + // foregroundColor: + // primaryColor, // button text color + // ), + // ), + // ), + // child: child!, + // ); + // }, + // ); + // if (datePick != null && + // datePick != birthDate) { + // birthDate = datePick; + // state.isNewBirthDate = true; + // setState(() { + // isBirthDateEmpty = false; + // }); + + // newBirthDate = + // "${birthDate.year}-${birthDate.month}-${birthDate.day}"; + // } + // }), + // CustomProfileTextField( + // keyboardType: TextInputType.number, + // pad: getProportionateScreenWidth(2), + // text: state.newEmail + // ? newEmail + // : result.email ?? '', + // hinttext: 'Tuliskan email Anda', + // title: 'Email', + // validate: validateEmail, + // onChanged: (value) { + // state.newEmail = true; + // newEmail = value; + // }, + // isErrorManual: isNameEmpty ?? false, + // textErrorManual: + // "Mohon masukkan email yang valid", + // ), + CustomProfileTextField( + keyboardType: TextInputType.number, + pad: getProportionateScreenWidth(2), + text: state.newPhone + ? newPhone + : result.phone ?? '', + hinttext: + 'Tuliskan nomor telepon Anda yang terdaftar', + title: 'Nomor Telepon / WA', + validate: validatePhone, + onChanged: (value) { + state.newPhone = true; + newPhone = value; + }, + isErrorManual: isNameEmpty ?? false, + textErrorManual: + "Mohon masukkan nomor telepon yang valid", + ), + // SizedBox( + // height: getProportionateScreenHeight(20), + // ), + // CustomProfileTextField( + // prefix: Padding( + // padding: EdgeInsets.only( + // top: getProportionateScreenHeight(10), + // left: getProportionateScreenWidth(20), + // right: + // getProportionateScreenHeight(10), + // ), + // child: FaIcon( + // FontAwesomeIcons.instagram, + // color: tenthColor), + // ), + // pad: getProportionateScreenWidth(2), + // text: state.newInstagram + // ? newInstagram + // : result.socialLink?.instagram ?? '', + // hinttext: 'http://instagram.com/', + // noTitle: true, + // onChanged: (value) { + // state.newInstagram = true; + // newInstagram = value; + // }, + // ), + // CustomProfileTextField( + // prefix: Padding( + // padding: EdgeInsets.only( + // top: getProportionateScreenHeight(10), + // left: getProportionateScreenWidth(20), + // right: + // getProportionateScreenHeight(10), + // ), + // child: FaIcon(FontAwesomeIcons.twitter, + // color: tenthColor), + // ), + // pad: getProportionateScreenWidth(2), + // text: state.newTwitter + // ? newTwitter + // : result.socialLink?.twitter ?? '', + // hinttext: 'http://twitter.com/', + // noTitle: true, + // onChanged: (value) { + // state.newTwitter = true; + // newTwitter = value; + // }, + // ), + // CustomProfileTextField( + // prefix: Padding( + // padding: EdgeInsets.only( + // top: getProportionateScreenHeight(10), + // left: getProportionateScreenWidth(20), + // right: + // getProportionateScreenHeight(10), + // ), + // child: FaIcon( + // FontAwesomeIcons.facebookF, + // color: tenthColor), + // ), + // pad: getProportionateScreenWidth(2), + // text: state.newFacebook + // ? newFacebook + // : result.socialLink?.facebook ?? '', + // hinttext: 'http://facebook.com/', + // noTitle: true, + // onChanged: (value) { + // state.newFacebook = true; + // newFacebook = value; + // }, + // ), + // CustomProfileTextField( + // prefix: Padding( + // padding: EdgeInsets.only( + // top: getProportionateScreenHeight(10), + // left: getProportionateScreenWidth(20), + // right: + // getProportionateScreenHeight(10), + // ), + // child: FaIcon( + // FontAwesomeIcons.linkedinIn, + // color: tenthColor), + // ), + // pad: getProportionateScreenWidth(2), + // text: state.newLinkedin + // ? newLinkedin + // : result.socialLink?.linkedin ?? '', + // hinttext: 'http://linkedin.com/', + // noTitle: true, + // onChanged: (value) { + // state.newLinkedin = true; + // newLinkedin = value; + // }, + // ), + ], + )), + ], + ), + ), + ) + ], + ), + ); + } else if (state.state == ResultStateData.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultStateData.Error) { + return Center( + child: TextButton( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Server internal Error ${state.message}'), + Icon(Icons.refresh) + ], + ), + onPressed: () {}), + ); + } else { + return Center(child: Text('')); + } + }, + ), + ), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/detail_pembelian_sukses.dart b/lib/screens/profile/account_sign_in/detail_pembelian_sukses.dart new file mode 100644 index 0000000..38f2612 --- /dev/null +++ b/lib/screens/profile/account_sign_in/detail_pembelian_sukses.dart @@ -0,0 +1,513 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/invoice.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/login_regist/default_button_payment.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/detail_invoice_provider.dart' + as detailProvs; + +class DetailPembelianSukses extends StatefulWidget { + DetailPembelianSukses({ + Key? key, + this.orderId, + this.dataHistoryTransactionModel, + }) : super(key: key); + + final String? orderId; + final HistoryTransactionModel? dataHistoryTransactionModel; + + final Map<String, String> status = { + 'null': "Dibatalkan", + '1': 'Berhasil', + '0': 'Transaksi Kursus Gratis', + '2': 'Menunggu Pembayaran', + '-1': 'Pembayaran Ditolak', + '-2': 'Melebihi Batas Waktu', + }; + + final Map<String, String> paymentType = { + 'null': "Dibatalkan", + 'bank transfer': 'Bank Transfer', + 'echannel': 'Bank Transfer', + 'credit card': 'Kartu Kredit', + 'permata': 'Bank Transfer', + 'gopay': 'GoPay', + 'cstore': 'Gerai', + 'free': 'Kursus Gratis', + 'Coupon Free Course': 'Kursus Gratis' + }; + + @override + State<DetailPembelianSukses> createState() => _DetailPembelianSuksesState(); +} + +class _DetailPembelianSuksesState extends State<DetailPembelianSukses> { + bool isButtonPressed = false; + final TextStyle baris = primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.orderId!.isNotEmpty) { + try { + Provider.of<detailProvs.DetailInvoiceProvider>(context, listen: false) + .fetchDetailInvoice(widget.orderId); + } catch (e) { + print('Gagal detail invoice $e'); + } + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + "Cara Pembayaran", + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(15), + ), + ), + ), + body: SingleChildScrollView( + child: Consumer<detailProvs.DetailInvoiceProvider>( + builder: (context, provider, child) { + if (provider.state == detailProvs.ResultState.loading) { + return Center( + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(100)), + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ), + ], + ), + ); + } else if (provider.state == detailProvs.ResultState.hasData) { + Widget buildTextWidget() { + String text = ""; + if (provider.detailInvoice?[0].permataVaNumber != null) { + text = "Permata Virtual Account"; + } else if (provider.detailInvoice?[0].billerCode != null) { + text = "Mandiri Virtual Account"; + } else if (provider.detailInvoice?[0].vaNumbers != null && + provider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bni')) { + text = "BNI Virtual Account"; + } else if (provider.detailInvoice?[0].vaNumbers != null && + provider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bca')) { + text = "BCA Virtual Account"; + } else if (provider.detailInvoice?[0].billerCode != null) { + text = "Mandiri Virtual Account"; + } else if (provider.detailInvoice?[0].store == "alfamart") { + text = "Alfamart"; + } else if (provider.detailInvoice?[0].store == "indomaret") { + text = "Indomart"; + } else if (provider.detailInvoice?[0].paymentType == + "credit_card") { + text = "Credit Card"; + } else if (provider.detailInvoice?[0].paymentType == "gopay") { + text = "QRIS"; + } else { + "Transaksi Gratis"; + } + return Text( + text ?? "Transaksi Gratis", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11)), + ); + } + + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10), + vertical: getProportionateScreenHeight(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(4), + topRight: Radius.circular(4), + ), + color: Theme.of(context).colorScheme.primaryContainer, + ), + padding: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(10), + top: getProportionateScreenHeight(15), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Transaksi Selesai', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + ), + ), + if (widget + .dataHistoryTransactionModel?.statusPayment == + '1' || + widget.dataHistoryTransactionModel + ?.statusPayment == + '0') + _statusLabel( + widget.status[widget.dataHistoryTransactionModel?.statusPayment] ?? + '', + eightColor) + else if (widget.dataHistoryTransactionModel!.statusPayment == '2' || + widget.dataHistoryTransactionModel!.statusPayment == + '0') + _statusLabel( + widget.status[widget.dataHistoryTransactionModel?.statusPayment] ?? + '', + fiveColor) + else if (widget.dataHistoryTransactionModel! + .statusPayment != + null && + widget.status.containsKey( + widget.dataHistoryTransactionModel!.statusPayment)) + _statusLabel(widget.status[widget.dataHistoryTransactionModel?.statusPayment] ?? '', sevenColor) + else + _statusLabel(widget.status['null']! ?? "null", sevenColor) + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Order ID', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + ), + ), + Text( + widget.orderId!, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Tanggal Pembelian', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + ), + ), + Text( + DateFormat('E, d MMM y').format(DateTime.parse( + widget.dataHistoryTransactionModel!.date + .toString())), + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + ], + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: + Theme.of(context).brightness == Brightness.dark + ? Colors.transparent + : secondaryColor.withOpacity(0.2), + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, getProportionateScreenHeight(4)), + ), + ], + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + Text( + "Kursus", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11)), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + listCourse( + imageUrl: provider.thumbnail, + title: widget.dataHistoryTransactionModel! + .courses?[0].title, + instructor: widget.dataHistoryTransactionModel! + .courses?[0].instructor, + price: widget.dataHistoryTransactionModel! + .courses?[0].price, + discountPrice: widget + .dataHistoryTransactionModel! + .courses?[0] + .price, + totalPrices: 0, + ), + ], + ), + Text( + 'Rincian Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + ), + ), + SizedBox(height: getProportionateScreenHeight(11)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Metode Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + buildTextWidget(), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Harga', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(provider.detailInvoice![0].grossAmount! ?? "0"))}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11)), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(2)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Potongan Kupon', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse("0"))}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11)), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Bayar', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(provider.detailInvoice![0].grossAmount! ?? "0"))}', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + fontWeight: semiBold, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + DefaultButtonPayment( + width: double.infinity, + height: 37, + text: "Lihat Invoice", + weight: semiBold, + press: () { + if (!isButtonPressed) { + isButtonPressed = true; + if (provider.detailInvoice?[0] + .transactionStatus == + 'settlement' || + provider.detailInvoice![0] + .transactionStatus == + 'capture') { + Navigator.push( + context, + CustomNavigator( + child: Invoice( + dataHistoryTransactionModel: + widget.dataHistoryTransactionModel, + ), + ), + ); + } else { + CherryToast.error( + animationDuration: Durations.long1, + toastDuration: Duration(seconds: 2), + title: Text( + "Pembayaran ini belum berhasil", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context); + } + Future.delayed(Duration(seconds: 2), () { + isButtonPressed = false; + }); + } + }, + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ), + ], + ), + ); + } else if (provider.state == detailProvs.ResultState.noData) { + return Center(child: Text(provider.message ?? '')); + } else if (provider.state == detailProvs.ResultState.error) { + return Center(child: Text(provider.message ?? '')); + } else { + return Container(); + } + }, + ), + ), + ); + } + + Widget listCourse({ + String? imageUrl, + String? title, + String? instructor, + String? price, + String? discountPrice, + int? totalPrices, + }) { + return Container( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(6), + bottom: getProportionateScreenHeight(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(60), + height: getProportionateScreenHeight(30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + image: NetworkImage(imageUrl == null || imageUrl.isEmpty + ? '$baseUrl/images/default-thumbnail.png' + : imageUrl.startsWith("http") + ? imageUrl + : '$baseUrl/uploads/thumbnail/course_thumbnails/$imageUrl'), + fit: BoxFit.cover, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _statusLabel(String text, Color color) { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(2), + horizontal: getProportionateScreenWidth(5)), + child: Text( + text, + style: thirdTextStyle.copyWith( + color: baruTextutih, + fontSize: getProportionateScreenWidth(9), + fontWeight: semiBold, + ), + ), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(5), + ), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/detail_transaksi.dart b/lib/screens/profile/account_sign_in/detail_transaksi.dart new file mode 100644 index 0000000..24681ca --- /dev/null +++ b/lib/screens/profile/account_sign_in/detail_transaksi.dart @@ -0,0 +1,924 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_countdown_timer/current_remaining_time.dart'; +import 'package:flutter_countdown_timer/flutter_countdown_timer.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/checkout/components/bar_batas_bayar.dart'; +import 'package:initial_folder/screens/checkout/components/tab_bar_batas_bayar.dart'; +import 'package:initial_folder/screens/checkout/gopay/payment_instruction_gopay.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator_pop.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/detail_invoice_provider.dart' + as detailProv; +import 'package:shared_preferences/shared_preferences.dart'; + +class DetailInvoice extends StatefulWidget { + const DetailInvoice({ + Key? key, + this.orderId, + this.dataHistoryTransactionModel, + }) : super(key: key); + + final String? orderId; + final HistoryTransactionModel? dataHistoryTransactionModel; + + @override + State<DetailInvoice> createState() => _DetailInvoiceState(); +} + +class _DetailInvoiceState extends State<DetailInvoice> { + Duration? remainingTime; + + final TextStyle baris = primaryTextStyle.copyWith( + fontWeight: reguler, + color: secondaryColor, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ); + + Future<void> saveRemainingTime(Duration remainingTime) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setInt('remainingTime', remainingTime.inSeconds); + } + + Future<void> loadRemainingTime() async { + final prefs = await SharedPreferences.getInstance(); + final remainingTimeInSeconds = prefs.getInt('remainingTime'); + if (remainingTimeInSeconds != null) { + remainingTime = Duration(seconds: remainingTimeInSeconds); + } + } + +Timer? countdownTimer; + + + @override +void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.dataHistoryTransactionModel?.dateExpired != null) { + final now = DateTime.now(); + final expiryTime = widget.dataHistoryTransactionModel!.dateExpired!; + setState(() { + remainingTime = expiryTime.isAfter(now) + ? expiryTime.difference(now) + : Duration.zero; + }); + + // Mulai timer hanya jika ada waktu tersisa + if (remainingTime != Duration.zero) { + startCountdownTimer(); + } + } + if (widget.orderId?.isNotEmpty ?? false) { + Provider.of<detailProv.DetailInvoiceProvider>(context, listen: false) + .fetchDetailInvoice(widget.orderId); + } + }); +} + +void startCountdownTimer() { + countdownTimer = Timer.periodic(Duration(seconds: 1), (_) { + if (remainingTime == null || remainingTime!.inSeconds <= 0) { + countdownTimer?.cancel(); + setState(() { + remainingTime = Duration.zero; + }); + } else { + setState(() { + remainingTime = remainingTime! - Duration(seconds: 1); + }); + print('Remaining time: $remainingTime'); + } + }); +} + + + @override +void dispose() { + countdownTimer?.cancel(); + if (remainingTime != null) { + saveRemainingTime(remainingTime!); + } + super.dispose(); +} + + Widget buildCountdownTimer() { + if (remainingTime == null || remainingTime!.inSeconds <= 0) { + return Text( + '00:00:00', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(10), + color: baruTextutih, + ), + ); + } + + final hours = remainingTime!.inHours.toString().padLeft(2, '0'); + final minutes = (remainingTime!.inMinutes % 60).toString().padLeft(2, '0'); + final seconds = (remainingTime!.inSeconds % 60).toString().padLeft(2, '0'); + + return Text( + '$hours:$minutes:$seconds', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(10), + color: baruTextutih, + ), + ); +} + + + + @override + Widget build(BuildContext context) { + PageProvider pageProvider = Provider.of<PageProvider>(context); + final themeProvider = Provider.of<ThemeProvider>(context); + + return Scaffold( + appBar: AppBar( + centerTitle: true, + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + "Cara Pembayaran", + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(15), + ), + ), + ), + body: SingleChildScrollView( + child: Consumer<detailProv.DetailInvoiceProvider>( + builder: (context, provider, child) { + if (provider.state == detailProv.ResultState.loading) { + return Center( + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(100)), + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ), + ], + ), + ); + } else if (provider.state == detailProv.ResultState.hasData) { + Widget buildTextWidget() { + String text = ""; + if (provider.detailInvoice![0].permataVaNumber != null) { + text = "Permata Virtual Account"; + } else if (provider.detailInvoice![0].billerCode != null) { + text = "Mandiri Virtual Account"; + } else if (provider.detailInvoice![0].vaNumbers != null && + provider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bni')) { + text = "BNI Virtual Account"; + } else if (provider.detailInvoice![0].vaNumbers != null && + provider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bca')) { + text = "BCA Virtual Account"; + } else if (provider.detailInvoice![0].billerCode != null) { + text = "Mandiri Virtual Account"; + } else if (provider.detailInvoice![0].store == "alfamart") { + text = "Alfamart"; + } else if (provider.detailInvoice![0].store == "indomaret") { + text = "Indomart"; + } else if (provider.detailInvoice![0].paymentType == + "credit_card") { + text = "Credit Card"; + } else { + text = "QRIS"; + } + return Text( + text, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ); + } + + String getVAText() { + String text = provider.detailInvoice![0].orderId!; + if (provider.detailInvoice != null && + provider.detailInvoice!.isNotEmpty) { + var invoice = provider.detailInvoice![0]; + if (invoice.permataVaNumber != null) { + text = invoice.permataVaNumber!; + } else if (invoice.billerCode != null) { + text = invoice.billKey!; + } else if (invoice.vaNumbers != null && + invoice.vaNumbers!.any((va) => va.bank == 'bni')) { + text = invoice.vaNumbers![0].vaNumber!; + } else if (invoice.vaNumbers != null && + invoice.vaNumbers!.any((va) => va.bank == 'bca')) { + text = invoice.vaNumbers![0].vaNumber!; + } else if (invoice.billerCode != null) { + text = invoice.billKey!; + } else if (invoice.store == "alfamart") { + text = provider.detailInvoice![0].paymentCode!; + } else if (invoice.store == "indomaret") { + text = provider.detailInvoice![0].paymentCode!; + } else if (provider.detailInvoice![0].paymentType == + "credit_card") { + text = provider.detailInvoice![0].maskedCard! + .replaceAll('-', ''); + } else { + text = provider.detailInvoice![0].orderId!; + } + } + return text; + } + + Widget buildVAaWidget() { + String text = getVAText(); + return Text( + text, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ); + } + + Widget buildImageWidget() { + String imageName = "qris"; + + if (provider.detailInvoice![0].permataVaNumber != null) { + imageName = "permata"; + } else if (provider.detailInvoice![0].billerCode != null) { + imageName = "mandiri"; + } else if (provider.detailInvoice![0].vaNumbers != null && + provider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bni')) { + imageName = "bni"; + } else if (provider.detailInvoice![0].vaNumbers != null && + provider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bca')) { + imageName = "bca"; + } else if (provider.detailInvoice![0].store == "alfamart") { + imageName = "alfamart"; + } else if (provider.detailInvoice![0].store == "indomaret") { + imageName = "indomaret"; + } else if (provider.detailInvoice![0].paymentType == + "credit_card") { + imageName = "qris"; + } + + return Container( + width: getProportionateScreenWidth(50), + height: getProportionateScreenWidth(17), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.brightness == + Brightness.dark + ? Colors.transparent + : (provider.detailInvoice![0].paymentType == + "gopay") + ? baruTexthitam.withOpacity(0.3) + : Colors.transparent, + spreadRadius: 1, + blurRadius: 3, + offset: Offset(0, 1), + ), + ], + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + image: AssetImage('assets/images/$imageName.png'), + ), + ), + ); + } + + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10), + vertical: getProportionateScreenHeight(10), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(4), + topRight: Radius.circular(4), + ), + color: Theme.of(context).colorScheme.primaryContainer, + ), + padding: EdgeInsets.only( + left: getProportionateScreenWidth(10), + right: getProportionateScreenWidth(10), + top: getProportionateScreenHeight(15), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Batas Waktu Pembayaran', + style: thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + SizedBox(height: getProportionateScreenHeight(2)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DateFormat('E, d MMM y HH:mm WIB').format( + DateTime.parse(provider + .detailInvoice![0].expiryTime!)), + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + Container( + decoration: BoxDecoration( + color: sevenColor, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(3), + vertical: getProportionateScreenHeight(2), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon( + Icons.access_time, + color: baruTextutih, + size: getProportionateScreenWidth(14), + ), + SizedBox( + width: getProportionateScreenWidth(2)), + buildCountdownTimer(), + ], + ), + ), + ], + ), + if (provider.detailInvoice![0].paymentType == "gopay") + SizedBox(height: getProportionateScreenHeight(5)), + if (provider.detailInvoice![0].paymentType == "gopay") + Divider( + color: secondaryColor, + thickness: 1, + ), + ], + ), + ), + Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(4), + bottomRight: Radius.circular(4), + ), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: + Theme.of(context).brightness == Brightness.dark + ? Colors.transparent + : secondaryColor.withOpacity(0.2), + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, getProportionateScreenHeight(4)), + ), + ], + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenHeight(10)), + Text( + "Kursus", + style: thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + listCourse( + imageUrl: provider.thumbnail, + title: widget.dataHistoryTransactionModel! + .courses?[0].title, + instructor: widget.dataHistoryTransactionModel! + .courses?[0].instructor, + price: widget.dataHistoryTransactionModel! + .courses?[0].price, + discountPrice: widget + .dataHistoryTransactionModel! + .courses?[0] + .price, + totalPrices: 0, + ), + ], + ), + if (provider.detailInvoice![0].paymentType == "gopay") + Divider( + color: secondaryColor, + thickness: 1, + ), + Text( + 'Metode Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + buildTextWidget(), + buildImageWidget(), + ], + ), + SizedBox(height: getProportionateScreenHeight(12)), + provider.detailInvoice![0].store == null && + provider.detailInvoice![0].paymentType != + "credit_card" + ? (provider.detailInvoice![0].paymentType != + "gopay") + ? Text( + "Nomor Virtual Akun", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ) + : provider.detailInvoice![0].paymentType != + "credit_card" + ? (provider.detailInvoice![0] + .paymentType != + "gopay") + ? Text( + "Nomor Pesananan", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth( + 10), + ), + ) + : Text( + "Order ID", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth( + 10), + ), + ) + : SizedBox.shrink() + : Text( + "Kode Pembayaran", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + if (provider.detailInvoice![0].paymentType == + "credit_card") + Text( + "Nomor Kartu Kredit", + style: primaryTextStyle.copyWith( + color: Colors.white, + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + buildVAaWidget(), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: getVAText().toString())) + .then( + (_) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Berhasil Menyalin Ke Clipboard'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + if (provider.detailInvoice?[0].billKey != null || + provider.detailInvoice?[0].store == "indomaret") + Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(5)), + child: Text( + "Merchant id", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + ), + if (provider.detailInvoice?[0].billKey != null) + Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(16)), + child: Row( + children: [ + Text( + provider.detailInvoice![0].billerCode!, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: provider + .detailInvoice![0].billerCode + .toString())) + .then( + (_) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Berhasil Menyalin Kode Pembayaran'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + color: primaryColor, + fontSize: + getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + ), + ], + ), + ), + if (provider.detailInvoice?[0].store == "indomaret") + Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight(10)), + child: Row( + children: [ + Text( + provider.detailInvoice![0].merchantId!, + style: primaryTextStyle.copyWith( + color: Colors.black, + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: provider + .detailInvoice![0].merchantId + .toString())) + .then( + (_) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Berhasil Menyalin Kode Merchant'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + color: primaryColor, + fontSize: + getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + ), + ], + ), + ), + Text( + 'Total Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(10), + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + children: [ + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(provider.detailInvoice![0].grossAmount!))}', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + Spacer(), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData( + text: double.parse(provider + .detailInvoice![0] + .grossAmount!) + .toString())) + .then( + (_) { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: Text( + 'Berhasil Menyalin Ke Clipboard'), + ), + ); + }, + ); + }, + child: Text( + "Salin", + style: thirdTextStyle.copyWith( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontSize: getProportionateScreenWidth(10), + fontWeight: reguler, + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(24)), + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(4), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + spreadRadius: 0, + blurRadius: 4, + offset: Offset(0, 4), + ), + ], + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(2), + right: getProportionateScreenWidth(2)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + provider.detailInvoice![0].paymentType != "gopay" + ? Container( + width: double.infinity, + padding: EdgeInsets.only( + top: getProportionateScreenHeight(15), + bottom: getProportionateScreenHeight(7), + left: getProportionateScreenWidth(18), + ), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(4), + topRight: Radius.circular(4), + ), + boxShadow: [ + BoxShadow( + color: secondaryColor.withOpacity(0.2), + spreadRadius: 0, + blurRadius: 2, + offset: Offset( + 0, getProportionateScreenHeight(4)), + ), + ], + ), + child: Text( + 'Cara Pembayaran', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + ) + : SizedBox.shrink(), + Container( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(5), + ), + child: provider.detailInvoice![0].store == + 'indomaret' || + provider.detailInvoice![0].store == + 'alfamart' + ? BarBatasBayar( + provider.detailInvoice![0].store!) + : (provider.detailInvoice![0].paymentType != + "gopay") + ? TabBarBatasBayar( + bank: provider.detailInvoice![0] + .vaNumbers != + null + ? provider.detailInvoice![0] + .vaNumbers![0].bank + : (provider.detailInvoice?[0] + .permataVaNumber != + null + ? "permata" + : provider.detailInvoice![0] + .paymentType), + ) + : PaymentInstructionGopay(), + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DefaultButton( + text: 'Belanja kursus lainnya', + weight: semiBold, + press: () { + pageProvider.currentIndex == 0; + Navigator.pushAndRemoveUntil( + context, + CustomNavigatorPop( + child: HomeScreen(), + ), + (route) => false); + }, + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: getProportionateScreenWidth(300), + height: getProportionateScreenHeight(38), + child: TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: Text( + "Cek Status Transaksi", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: semiBold, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + letterSpacing: 0.5, + ), + ), + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + side: BorderSide( color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode), + borderRadius: BorderRadius.circular( + getProportionateScreenWidth(10)), + ), + backgroundColor: Colors.transparent, + ), + ), + ), + ], + ), + ], + ), + ); + } else if (provider.state == detailProv.ResultState.noData) { + return Center(child: Text(provider.message ?? '')); + } else if (provider.state == detailProv.ResultState.error) { + return Center(child: Text(provider.message ?? '')); + } else { + return Container(); + } + }, + ), + ), + ); + } + + Widget listCourse({ + String? imageUrl, + String? title, + String? instructor, + String? price, + String? discountPrice, + int? totalPrices, + }) { + return Container( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(6), + bottom: getProportionateScreenHeight(10), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(60), + height: getProportionateScreenHeight(30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + image: NetworkImage(imageUrl == null || imageUrl.isEmpty + ? '$baseUrl/images/default-thumbnail.png' + : imageUrl.startsWith("http") + ? imageUrl + : '$baseUrl/uploads/thumbnail/course_thumbnails/$imageUrl'), + fit: BoxFit.cover, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/incomplete_profile_screen.dart b/lib/screens/profile/account_sign_in/incomplete_profile_screen.dart new file mode 100644 index 0000000..4aa0e76 --- /dev/null +++ b/lib/screens/profile/account_sign_in/incomplete_profile_screen.dart @@ -0,0 +1,377 @@ +// this screen is only when the profile (name, email, phone number, password) is not complete + +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/models/data_diri_model.dart'; +import 'package:initial_folder/providers/data_diri_provider.dart'; +import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +import 'package:initial_folder/providers/incomplete_profile_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_screen.dart'; +import 'package:initial_folder/services/user_info_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/custom_profile_text_field.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/footer.dart'; +import 'package:initial_folder/widgets/login_regist/header.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/helper/user_info.dart'; + +class IncompleteProfile extends StatefulWidget { + static String routeName = "/uncomplete_profile"; + IncompleteProfile({Key? key}) : super(key: key); + + @override + _IncompleteProfileState createState() => _IncompleteProfileState(); +} + +class _IncompleteProfileState extends State<IncompleteProfile> { + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + + final TextEditingController reEmailController = + TextEditingController(text: ''); + final TextEditingController passwordController = + TextEditingController(text: ''); + final TextEditingController rePasswordController = + TextEditingController(text: ''); + + bool isLoading = false; + bool _isObscure = true; + bool _isObscure1 = true; + + bool? isEmailEmpty = false; + bool? isPhoneEmpty = false; + bool? isNameEmpty = false; + + late String newName = ''; + late String newEmail = ''; + late String newPhone = ''; + late String password = ''; + late String rePassword = ''; + + @override + Widget build(BuildContext context) { + SizeConfig().init(context); + + IncompleteProfileProvider incompleteProfileProvider = + Provider.of<IncompleteProfileProvider>(context); + + Future _showMessage(String text) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: EdgeInsets.fromLTRB(22, 30, 22, 30), + content: Text( + text, + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + ), + ); + } + + handleProfile(DataDiriProvider state, DataOfDataDiriModel result) async { + setState(() { + isLoading = true; + }); + + if (await incompleteProfileProvider.updateIncompleteProfile( + fullname: state.newName ? newName : result.fullname ?? '', + email: state.newEmail ? newEmail : result.email ?? '', + phone: state.newPhone ? newPhone : result.phone ?? '', + newPassword: password, + newConfirmPassword: rePassword)) { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, (Route<dynamic> route) => false); + } else { + if (incompleteProfileProvider.updateIncompleteProfileModel != null) { + dynamic updateIncompleteProfileMessages = + incompleteProfileProvider.updateIncompleteProfileModel!.messages; + if (updateIncompleteProfileMessages is! String) { + // validate name from backend + if (updateIncompleteProfileMessages.fullname is String) { + setState(() { + isNameEmpty = true; + }); + } + // validate datebirth from backend + if (updateIncompleteProfileMessages.phone is String) { + setState(() { + isPhoneEmpty = true; + }); + } + // validate datebirth from backend + if (updateIncompleteProfileMessages.email is String) { + setState(() { + isEmailEmpty = true; + }); + } + } else { + if (incompleteProfileProvider + .updateIncompleteProfileModel!.status == + 404) { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + + _showMessage("Sesi habis silahkan login ulang"); + } else { + _showMessage(updateIncompleteProfileMessages); + } + } + } + } + + setState(() { + isLoading = false; + }); + } + + void _validateInputs(DataDiriProvider state, DataOfDataDiriModel result) { + if (this._formKey.currentState!.validate()) { + handleProfile(state, result); + } + } + + Widget form(DataDiriProvider state, DataOfDataDiriModel result) { + Widget nameInput() { + return CustomProfileTextField( + pad: getProportionateScreenWidth(16), + text: state.newName ? newName : result.fullname ?? '', + hinttext: 'Masukan nama lengkap', + title: 'Nama Lengkap', + validate: validateName, + onChanged: (value) { + isNameEmpty = false; + state.newName = true; + newName = value; + }, + isErrorManual: isNameEmpty ?? false, + textErrorManual: "Nama lengkap tidak boleh kosong", + ); + } + + Widget emailInput() { + return CustomProfileTextField( + pad: getProportionateScreenWidth(16), + text: state.newEmail ? newEmail : result.email ?? '', + hinttext: 'Masukan email', + title: 'Email', + validate: validateEmail, + onChanged: (value) { + isEmailEmpty = false; + state.newEmail = true; + newEmail = value; + }, + isErrorManual: isEmailEmpty ?? false, + textErrorManual: "Mohon masukkan email yang valid", + ); + } + + Widget reEmailInput() { + return CustomProfileTextField( + pad: getProportionateScreenWidth(16), + text: '', + hinttext: 'Masukan konfirmasi email', + title: 'Konfirmasi Email', + validate: (String? value) { + return validateReEmail( + value, state.newEmail ? newEmail : result.email ?? ''); + }, + ); + } + + Widget phoneNumberInput() { + return CustomProfileTextField( + pad: getProportionateScreenWidth(16), + text: state.newPhone ? newPhone : result.phone ?? '', + hinttext: 'Masukan no hp', + title: 'No Telepon', + validate: validatePhone, + onChanged: (value) { + isPhoneEmpty = false; + state.newPhone = true; + newPhone = value; + }, + isErrorManual: isPhoneEmpty ?? false, + textErrorManual: "Mohon masukkan nomor telepon yang valid", + ); + } + + Widget passwordInput() { + return CustomProfileTextField( + pad: getProportionateScreenWidth(16), + text: '', + hinttext: 'Masukan password', + title: 'Password', + validate: validatePassword, + onChanged: (value) { + password = value; + }, + obscuretext: _isObscure, + suffix: GestureDetector( + onTap: () => setState(() { + _isObscure = !_isObscure; + }), + child: _isObscure + ? Icon( + FeatherIcons.eyeOff, + color: secondaryColor, + size: 18, + ) + : Icon( + FeatherIcons.eye, + color: secondaryColor, + size: 18, + )), + ); + } + + Widget rePasswordInput() { + return CustomProfileTextField( + pad: getProportionateScreenWidth(16), + text: '', + hinttext: 'Masukan password', + title: 'Konfirmasi Password', + validate: (String? value) { + return validateRePassword(value, password); + }, + onChanged: (value) { + rePassword = value; + }, + obscuretext: _isObscure1, + suffix: GestureDetector( + onTap: () => setState(() { + _isObscure1 = !_isObscure1; + }), + child: _isObscure1 + ? Icon( + FeatherIcons.eyeOff, + color: secondaryColor, + size: 18, + ) + : Icon( + FeatherIcons.eye, + color: secondaryColor, + size: 18, + )), + ); + } + + Widget button() { + return Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(15), + right: getProportionateScreenWidth(15), + top: getProportionateScreenHeight(3)), + child: isLoading + ? LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: Color(0xff050505)) + : DefaultButton( + text: 'Simpan', + press: () { + _validateInputs(state, result); + }, + ), + ); + } + + return Form( + key: _formKey, + child: Column( + children: [ + phoneNumberInput(), + passwordInput(), + rePasswordInput(), + SizedBox( + height: getProportionateScreenHeight(8), + ), + button(), + ], + ), + ); + } + + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + onPressed: () async { + final provider = Provider.of<FirebaseAuthenticationProvider>( + context, + listen: false); + + await UsersInfo().logout(); + await provider.logout(); + + Navigator.of(context).pushNamedAndRemoveUntil( + RegistrationScreen.routeName, + (Route<dynamic> route) => false); + }, + icon: Icon(Icons.arrow_back), + iconSize: getProportionateScreenWidth(18), + padding: EdgeInsets.all(getProportionateScreenWidth(8)), + ), + Container( + child: Align( + alignment: Alignment.center, + child: Header( + text: 'Yuk Lengkapi Profile kamu sebelum memulai', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5)), + ), + ), + SizedBox( + height: getProportionateScreenHeight(32), + ), + ChangeNotifierProvider( + create: (context) => + DataDiriProvider(userInfoService: UserInfoService()), + child: + Consumer<DataDiriProvider>(builder: (context, state, _) { + if (state.state == ResultStateData.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultStateData.HasData) { + var result = state.result!.data[0]; + return form(state, result); + } else if (state.state == ResultStateData.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultStateData.Error) { + return Center( + child: TextButton( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text('Server internal Error ${state.message}'), + Icon(Icons.refresh) + ], + ), + onPressed: () {}), + ); + } else { + return Center(child: Text('Gagal mengambil data')); + } + })), + SizedBox(height: getProportionateScreenHeight(32)), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/invoice.dart b/lib/screens/profile/account_sign_in/invoice.dart new file mode 100644 index 0000000..eae05d1 --- /dev/null +++ b/lib/screens/profile/account_sign_in/invoice.dart @@ -0,0 +1,491 @@ +import 'dart:typed_data'; +import 'dart:ui' as ui; +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button_payment.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter/rendering.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:initial_folder/providers/detail_invoice_provider.dart' + as invProv; + +class Invoice extends StatefulWidget { + Invoice({ + super.key, + this.dataHistoryTransactionModel, + }); + + final HistoryTransactionModel? dataHistoryTransactionModel; + + @override + State<Invoice> createState() => _InvoiceState(); +} + +class _InvoiceState extends State<Invoice> { + final GlobalKey _globalKey = GlobalKey(); + bool _isDownloading = false; + + Future<void> _captureAndSave() async { + setState(() { + _isDownloading = true; + }); + + RenderRepaintBoundary boundary = + _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary; + ui.Image image = await boundary.toImage(); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + + if (!(await Permission.storage.status.isGranted)) { + await Permission.storage.request(); + } + + final result = await ImageGallerySaver.saveImage( + pngBytes, + quality: 100, + name: "invoice_${widget.dataHistoryTransactionModel!.orderId}", + ); + + setState(() { + _isDownloading = false; + }); + print(result); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Invoice sudah terdownload!')), + ); + } + + @override + Widget build(BuildContext context) { + var dataUser = Provider.of<UserInfoProvider>(context, listen: false).result; + + return Scaffold( + appBar: AppBar( + centerTitle: true, + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + "Invoice", + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(15), + ), + ), + ), + body: SingleChildScrollView( + child: Consumer<invProv.DetailInvoiceProvider>( + builder: (context, invoiceProvider, child) { + if (invoiceProvider.state == invProv.ResultState.loading) { + return Center(child: CircularProgressIndicator()); + } else if (invoiceProvider.state == invProv.ResultState.hasData) { + Widget buildTextWidget() { + String text = ""; + if (invoiceProvider.detailInvoice![0].permataVaNumber != null) { + text = "Permata Virtual Account"; + } else if (invoiceProvider.detailInvoice![0].billerCode != + null) { + text = "Mandiri Virtual Account"; + } else if (invoiceProvider.detailInvoice![0].vaNumbers != + null && + invoiceProvider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bni')) { + text = "BNI Virtual Account"; + } else if (invoiceProvider.detailInvoice![0].vaNumbers != + null && + invoiceProvider.detailInvoice![0].vaNumbers! + .any((va) => va.bank == 'bca')) { + text = "BCA Virtual Account"; + } else if (invoiceProvider.detailInvoice![0].billerCode != + null) { + text = "Mandiri Virtual Account"; + } else if (invoiceProvider.detailInvoice![0].store == + "alfamart") { + text = "Alfamart"; + } else if (invoiceProvider.detailInvoice![0].store == + "indomaret") { + text = "Indomart"; + } else if (invoiceProvider.detailInvoice![0].paymentType == + "credit_card") { + text = "Credit Card"; + } else { + text = "Transaski Gratis"; + } + return Text( + text, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11)), + ); + } + + return Center( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(25)), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + RepaintBoundary( + key: _globalKey, + child: Stack( + children: [ + Image.asset( + Theme.of(context).colorScheme.brightness == + Brightness.dark + ? "assets/images/invoices_dark.png" + : "assets/images/invoices.png", + width: getProportionateScreenWidth(370), + height: getProportionateScreenHeight(470), + fit: BoxFit.fill, + ), + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(25), + right: getProportionateScreenWidth(25), + top: getProportionateScreenHeight(70), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Order ID', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11), + ), + ), + Text( + widget.dataHistoryTransactionModel! + .orderId!, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11), + ), + ), + ], + ), + SizedBox( + height: getProportionateScreenHeight(5)), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Tanggal Pembelian', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11), + ), + ), + Text( + DateFormat('E, d MMM y').format( + DateTime.parse(widget + .dataHistoryTransactionModel! + .date + .toString())), + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 11)), + ), + ], + ), + SizedBox( + height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + 'Informasi Pembeli', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11), + ), + ), + ], + ), + Divider( + color: secondaryColor, + thickness: 0.5, + height: 20, + ), + Text( + 'Nama', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(9), + ), + ), + SizedBox( + height: getProportionateScreenHeight(5)), + Text( + dataUser!.data[0].fullname!, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11)), + ), + SizedBox( + height: getProportionateScreenHeight(6)), + Text( + 'Email', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(9), + ), + ), + SizedBox( + height: getProportionateScreenHeight(5)), + Text( + dataUser.data[0].email!, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11)), + ), + SizedBox( + height: getProportionateScreenHeight(10)), + Text( + "Kursus", + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11)), + ), + Divider( + color: secondaryColor, + thickness: 0.5, + height: 20, + ), + Column( + children: widget + .dataHistoryTransactionModel!.courses! + .map((e) => listCourse( + imageUrl: e.thumbnail, + title: e.title, + instructor: e.instructor, + price: e.price, + )) + .toList(), + ), + SizedBox( + height: getProportionateScreenHeight(15)), + Text( + "Informasi Pembayaran", + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(11)), + ), + Divider( + color: secondaryColor, + thickness: 0.5, + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Metode Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + buildTextWidget(), + ], + ), + Divider( + color: secondaryColor, + thickness: 0.5, + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Harga', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(invoiceProvider.detailInvoice![0].grossAmount! ?? "0"))}', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 11)), + ), + ], + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Potongan Kupon', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse("0"))}', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 11)), + ), + ], + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Biaya Layanan', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse("0"))}', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 11)), + ), + ], + ), + Divider( + color: secondaryColor, + thickness: 0.5, + height: 20, + ), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total Bayar', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth(10), + ), + ), + Text( + 'Rp. ${NumberFormat.currency(locale: 'id', symbol: '', decimalDigits: 0).format(double.parse(invoiceProvider.detailInvoice![0].grossAmount! ?? "0"))}', + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(13), + fontWeight: semiBold, + ), + ), + ], + ), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + ], + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + _isDownloading + ? CircularProgressIndicator() + : DefaultButtonPayment( + text: "Download", + press: () { + _captureAndSave(); + }, + width: double.infinity, + height: 37, + isInvoice: true, + ), + ], + ), + ), + ); + } else if (invoiceProvider.state == invProv.ResultState.noData) { + return Center(child: Text('No Data')); + } else if (invoiceProvider.state == invProv.ResultState.error) { + return Center(child: Text('Error')); + } else { + return Center(child: Text('Unexpected state')); + } + }, + ), + ), + ); + } + + Widget listCourse({ + String? imageUrl, + String? title, + String? instructor, + String? price, + String? discountPrice, + int? totalPrices, + }) { + return Container( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(6), + bottom: getProportionateScreenHeight(12), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(60), + height: getProportionateScreenHeight(30), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + image: DecorationImage( + image: NetworkImage(imageUrl != null && imageUrl.isNotEmpty + ? "$baseUrl/uploads/thumbnail/course_thumbnails/$imageUrl" + : "https://api.vokasia.id/images/default-thumbnail.png"), + fit: BoxFit.cover, + ), + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 7, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/riwayat_transaksi.dart b/lib/screens/profile/account_sign_in/riwayat_transaksi.dart new file mode 100644 index 0000000..d37eb79 --- /dev/null +++ b/lib/screens/profile/account_sign_in/riwayat_transaksi.dart @@ -0,0 +1,368 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/detail_invoice_provider.dart'; +import 'package:initial_folder/providers/history_transactions_provider.dart' + as historyProv; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/detail_pembelian_sukses.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi_pending.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/custom_navigator_pop.dart'; +import 'package:initial_folder/widgets/riwayat_list.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class RiwayatTransaksi extends StatefulWidget { + RiwayatTransaksi({Key? key}) : super(key: key); + static const String routeName = "/riwayat-transaksi"; + + @override + State<RiwayatTransaksi> createState() => _RiwayatTransaksiState(); +} + +class _RiwayatTransaksiState extends State<RiwayatTransaksi> { + bool isButtonPressed = false; + + Future<bool> _onWillPop() async { + Provider.of<PageProvider>(context, listen: false).currentIndex = 4; + Navigator.pushAndRemoveUntil( + context, + CustomNavigatorPop( + child: HomeScreen(), + ), + (route) => false, + ); + return Future.value(false); + } + + @override + void initState() { + super.initState(); + // Memanggil fungsi getHistoryTransaction untuk refresh data ketika halaman pertama kali dibuka + WidgetsBinding.instance.addPostFrameCallback((_) { + Provider.of<historyProv.HistoryTranscationsProvider>(context, listen: false) + .getHistoryTransaction(); + }); + } + + @override + Widget build(BuildContext context) { + var selected = Provider.of<DetailInvoiceProvider>(context); + var historyProvider = + Provider.of<historyProv.HistoryTranscationsProvider>(context); + final selectedTotalPrices = Provider.of<TotalPriceProvider>(context); + PageProvider pageProvider = Provider.of<PageProvider>(context); + + return WillPopScope( + onWillPop: _onWillPop, + child: SafeArea( + child: Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Riwayat Pembelian', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(15), + ), + ), + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: Theme.of(context).colorScheme.onBackground, + size: getProportionateScreenWidth(17), + ), + onPressed: () { + _onWillPop(); + }, + ), + ), + body: Column( + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + CustomNavigator( + child: RiwayatTransaksiPending(), + ), + ); + }, + child: Container( + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(12)), + margin: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(10), + horizontal: getProportionateScreenWidth(16)), + decoration: BoxDecoration( + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor.withOpacity(0.3) + : baruTextutih, + borderRadius: BorderRadius.circular(5), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + Icon( + Icons.pending_actions, + color: Theme.of(context).brightness == Brightness.dark + ? secondaryColor + : baruTexthitam, + size: getProportionateScreenWidth(17), + ), + SizedBox(width: getProportionateScreenWidth(15)), + Text( + 'Menunggu Pembayaran (${historyProvider.paymentPending?.length ?? 0})', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(15), + color: + Theme.of(context).brightness == Brightness.dark + ? secondaryColor + : baruTexthitam, + ), + ) + ], + ), + Icon(Icons.chevron_right) + ], + ), + ), + ), + Consumer<historyProv.HistoryTranscationsProvider>( + builder: (context, state, _) { + if (state.state == historyProv.ResultState.loading) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(70), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(70), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + ), + ); + } else if (state.state == historyProv.ResultState.noData) { + return Center( + child: Text(state.message), + ); + } else if (state.state == historyProv.ResultState.hasData) { + List<HistoryTransactionModel> history = state.historyPayment!; + return Expanded( + child: RefreshIndicator( + onRefresh: () async { + await Provider.of< + historyProv.HistoryTranscationsProvider>( + context, + listen: false) + .getHistoryTransaction(); + }, + color: primaryColor, + strokeWidth: 2, + child: ListView.builder( + + physics: ScrollPhysics(), + itemCount: history.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + selectedTotalPrices.selectedTotalPrices = history[index].totalPrice ?? 0; + selected.selectedThumbnail = history[index].courses?.isNotEmpty == true + ? history[index].courses![0].thumbnail ?? 'default_thumbnail.png' + : 'default_thumbnail.png'; + if (!isButtonPressed) { + setState(() { + isButtonPressed = true; + }); + if (history[index].statusPayment != "0") { + Navigator.push( + context, + CustomNavigator( + child: DetailPembelianSukses( + orderId: history[index].orderId!, + dataHistoryTransactionModel: + history[index], + ), + ), + ); + } else { + CherryToast.error( + animationDuration: Durations.medium1, + toastDuration: Duration(seconds: 2), + title: Text( + "Pembayaran gratis tidak memiliki invoice", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context); + } + Future.delayed(Duration(seconds: 2), () { + isButtonPressed = false; + }); + } + }, + child: RiwayatList( + dataHistoryTransactionModel: history[index], + ), + ); + }, + ), + ), + ); + } else if (state.state == historyProv.ResultState.error) { + return RefreshIndicator( + onRefresh: () async { + await Provider.of< + historyProv.HistoryTranscationsProvider>( + context, + listen: false) + .getHistoryTransaction(); + }, + child: Center( + child: Text(state.message), + ), + ); + } else { + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } + }, + ), + ], + ), + ),), + ); + + + } +} diff --git a/lib/screens/profile/account_sign_in/riwayat_transaksi_pending.dart b/lib/screens/profile/account_sign_in/riwayat_transaksi_pending.dart new file mode 100644 index 0000000..3810948 --- /dev/null +++ b/lib/screens/profile/account_sign_in/riwayat_transaksi_pending.dart @@ -0,0 +1,238 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/history_transactions_provider.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/riwayat_list_delete.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/order_provider.dart' as orderProvider; +import 'package:shimmer/shimmer.dart'; + +class RiwayatTransaksiPending extends StatelessWidget { + const RiwayatTransaksiPending({Key? key}) : super(key: key); + static const String routeName = "/riwayat-transaksi-pending"; + + @override + Widget build(BuildContext context) { + Provider.of<orderProvider.OrderProvider>(context, listen: false).clear(); + + return WillPopScope( + onWillPop: () async { + Navigator.pushReplacementNamed(context, RiwayatTransaksi.routeName); + return false; + }, + child: SafeArea( + child: Scaffold( + appBar: AppBar( + centerTitle: true, + scrolledUnderElevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text( + 'Menunggu Pembayaran', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(15), + ), + ), + ), + body: StreamBuilder<List<HistoryTransactionModel>>( + stream: + Provider.of<HistoryTranscationsProvider>(context, listen: false) + .getHistoryTransactionStream(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15)), + child: Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(70), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(70), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Container( + width: getProportionateScreenWidth(130), + height: getProportionateScreenHeight(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Container( + width: double.infinity, + height: getProportionateScreenHeight(90), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: Colors.white, + ), + ), + ], + ), + ), + ); + } else if (snapshot.hasError) { + return Text(snapshot.error.toString()); + } else { + List<HistoryTransactionModel>? history = snapshot.data; + + return history?.length == 0 + ? Center( + child: Text('Belum ada pembayaran dalam status menunggu'), + ) + : RefreshIndicator( + onRefresh: () async { + await Provider.of<HistoryTranscationsProvider>(context, + listen: false) + .getHistoryTransaction(); + }, + color: primaryColor, + strokeWidth: 2, + child: Container( + margin: EdgeInsets.only( + top: getProportionateScreenHeight(15)), + child: ListView.builder( + itemCount: history?.length, + itemBuilder: (context, index) { + return RiwayatListDelete( + dataHistoryTransactionModel: history![index], + onPaymentCancelled: (message) async { + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Theme.of(context) + .colorScheme + .background, + elevation: 0.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + contentPadding: + EdgeInsets.fromLTRB(12, 26, 22, 15), + content: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight(14)), + child: Text( + textAlign: TextAlign.center, + message, + style: thirdTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(12), + ), + ), + ), + ), + ); + await Provider.of<HistoryTranscationsProvider>( + context, + listen: false) + .getHistoryTransaction(); + }, + ); + }, + ), + ), + ); + } + }, + ), + ),), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/setting_akun.dart b/lib/screens/profile/account_sign_in/setting_akun.dart new file mode 100644 index 0000000..ab2b904 --- /dev/null +++ b/lib/screens/profile/account_sign_in/setting_akun.dart @@ -0,0 +1,315 @@ +import 'package:cherry_toast/cherry_toast.dart'; +import 'package:cherry_toast/resources/arrays.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/providers/update_password_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/custom_text_form_field.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import 'package:tap_debouncer/tap_debouncer.dart'; + +class SettingAkun extends StatefulWidget { + @override + State<SettingAkun> createState() => _SettingAkunState(); +} + +class _SettingAkunState extends State<SettingAkun> { + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + + final TextEditingController _currentPasswordController = + TextEditingController(text: ''); + + final TextEditingController _newPasswordController = + TextEditingController(text: ''); + + final TextEditingController _confirmPasswordController = + TextEditingController(text: ''); + bool _isObscure = true; + bool _isObscure1 = true; + bool _isObscure2 = true; + bool _isloading = true; + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + UserInfoProvider userInfoProvider = Provider.of<UserInfoProvider>(context); + UpdatePasswordProvider updatePasswordProvider = + Provider.of<UpdatePasswordProvider>(context); + // EmailProvider emailProvider = Provider.of<EmailProvider>(context); + // ShowHidePassword showHidePassword = Provider.of<ShowHidePassword>(context); + // ShowHidePassword1 showHidePassword1 = + // Provider.of<ShowHidePassword1>(context); + // ShowHidePassword2 showHidePassword2 = + // Provider.of<ShowHidePassword2>(context); + + Future _showMessage(String text) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + backgroundColor: Colors.white, + surfaceTintColor: Colors.transparent, + contentPadding: EdgeInsets.fromLTRB(22, 30, 22, 30), + content: Text( + text, + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + ), + ); + } + + updatePassword( + {required idUser, + String? email, + String? oldPassword, + String? password, + String? newPasswordConfirm}) async { + await Provider.of<UpdatePasswordProvider>(context, listen: false) + .passwordUpdate( + idUser: idUser, + email: email, + oldPassword: oldPassword, + password: password, + newPasswordConfirm: newPasswordConfirm) + .then((value) => { + setState(() { + if (value == true) { + _showMessage('Password Berhasil Diubah'); + _isloading = true; + } else { + setState(() { + _isloading = true; + print('masuk sini'); + }); + _showMessage('Password Gagal Diubah'); + _isloading = true; + } + }) + }); + } + + void _validateInputs() async { + setState(() { + _isloading = false; + }); + + if (this._formKey.currentState!.validate()) { + await updatePassword( + idUser: userInfoProvider.result!.data[0].idUser, + email: userInfoProvider.result!.data[0].email, + oldPassword: _currentPasswordController.text, + password: _newPasswordController.text, + newPasswordConfirm: _confirmPasswordController.text, + ); + } else { + setState(() { + _isloading = true; + print('masuk sini'); + }); + CherryToast.error( + toastPosition: Position.top, + animationDuration: Durations.long1, + title: Text( + "Formulir tidak valid, periksa kembali", + style: TextStyle( + color: Colors.black, + fontSize: 15, + ), + ), + animationType: AnimationType.fromTop, + ).show(context); + } + } + + Widget form() { + Widget currentPasswordInput() { + return CustomTextField( + pad: getProportionateScreenWidth(2), + controler: _currentPasswordController, + hinttext: 'Masukkan password saat ini', + title: 'Password Lama', + obscuretext: _isObscure, + validate: (String? value) { + if (value!.length == 0) + return 'Password tidak boleh kosong'; + else if (value.length < 8) + return 'Password minimal harus berjumlah 8 karakter'; + + return null; + }, + suffix: GestureDetector( + onTap: () => setState(() { + _isObscure = !_isObscure; + }), + child: _isObscure + ? Icon( + Icons.visibility_off, + color: secondaryColor, + size: 18, + ) + : Icon( + Icons.visibility, + color: secondaryColor, + size: 18, + )), + ); + } + + Widget newPasswordInput() { + return CustomTextField( + pad: getProportionateScreenWidth(2), + controler: _newPasswordController, + hinttext: 'Masukkan password baru', + title: 'Password Baru', + obscuretext: _isObscure1, + validate: (String? value) { + if (value!.length == 0) + return 'Password tidak boleh kosong'; + else if (value.length < 8) + return 'Password minimal harus berjumlah 8 karakter'; + else if (value == _currentPasswordController.text) { + return 'Password tidak boleh sama'; + } + return null; + }, + suffix: GestureDetector( + onTap: () { + setState(() { + _isObscure1 = !_isObscure1; + }); + }, + child: _isObscure1 + ? Icon( + Icons.visibility_off, + color: secondaryColor, + size: 18, + ) + : Icon( + Icons.visibility, + color: secondaryColor, + size: 18, + )), + ); + } + + Widget confirmPasswordInput() { + return CustomTextField( + pad: getProportionateScreenWidth(2), + controler: _confirmPasswordController, + hinttext: 'Konfirmasi password baru', + title: 'Konfirmasi Password', + obscuretext: _isObscure2, + suffix: GestureDetector( + onTap: () => setState(() { + _isObscure2 = !_isObscure2; + }), + child: _isObscure2 + ? Icon( + Icons.visibility_off, + color: secondaryColor, + size: 18, + ) + : Icon( + Icons.visibility, + color: secondaryColor, + size: 18, + )), + validate: (String? value) { + if (value!.length == 0) + return 'Password tidak boleh kosong'; + else if (value.length < 8) + return 'Password minimal harus berjumlah 8 karakter'; + else if (value != _newPasswordController.text) { + return 'Password baru dengan konfirmasi tidak sama'; + } + return null; + }, + ); + } + + Widget bottomNav() { + return _isloading != true + ? Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(45), + vertical: getProportionateScreenHeight(10)), + child: LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: Color(0xff050505))) + : Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(45), + vertical: getProportionateScreenHeight(10)), + child: TapDebouncer( + cooldown: const Duration(milliseconds: 3000), + onTap: () async => + await {_validateInputs()}, // your tap handler moved here + builder: (BuildContext context, TapDebouncerFunc? onTap) { + return DefaultButton( + text: 'Simpan perubahan', + press: onTap, + ); + }, + ), + ); + } + + return Form( + key: _formKey, + child: Column( + children: [ + currentPasswordInput(), + SizedBox(height: getProportionateScreenHeight(15)), + newPasswordInput(), + SizedBox(height: getProportionateScreenHeight(15)), + confirmPasswordInput(), + SizedBox(height: getProportionateScreenHeight(70)), + bottomNav(), + ], + ), + ); + } + + return SafeArea( + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + title: Text( + 'Pengaturan Akun', + style: secondaryTextStyle.copyWith( + letterSpacing: 0.23, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14)), + )), + body: ListView( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + form(), + SizedBox( + height: getProportionateScreenHeight(13), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/profile/account_sign_in/sign_in_screen.dart b/lib/screens/profile/account_sign_in/sign_in_screen.dart new file mode 100644 index 0000000..78db1a5 --- /dev/null +++ b/lib/screens/profile/account_sign_in/sign_in_screen.dart @@ -0,0 +1,605 @@ +import 'dart:io'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/providers/profile_image_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/screens/certificate/certificate.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/data_diri.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/riwayat_transaksi.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/setting_akun.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/screens/profile/components/about_profile_list.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/terms_and_privacy.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/history_transactions_provider.dart' + as historyProvider; + +class SignInScreen extends StatefulWidget { + const SignInScreen({ + Key? key, + }) : super(key: key); + + @override + State<SignInScreen> createState() => _SignInScreenState(); +} + +class _SignInScreenState extends State<SignInScreen> { + final String frontEndUrl = "https://vocasia.id"; + bool isLoading = false; + final double maximumRadius = 1000; + bool showCircle = true; + + @override + Widget build(BuildContext context) { + ProfileImageProvider profileImageProvider = + Provider.of<ProfileImageProvider>(context, listen: false); + final ImagePicker _picker = ImagePicker(); + final themeProvider = Provider.of<ThemeProvider>(context); + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + Future _showMessage(String text) { + return showDialog( + context: context, + builder: (context) => AlertDialog( + contentPadding: const EdgeInsets.fromLTRB(22, 30, 22, 30), + content: Text( + text, + textAlign: TextAlign.center, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + ), + ); + } + + void takePhoto(ImageSource source) async { + try { + final XFile? pickedFile = await _picker.pickImage(source: source); + var imageFile = (pickedFile != null) ? File(pickedFile.path) : File(''); + + if (await profileImageProvider.addProfileImage(pckFile: imageFile)) { + setState(() { + isLoading = true; + }); + await Future.delayed(const Duration(seconds: 2)); + profileImageProvider.setImageFile(imageFile); + setState(() { + imageFile; + isLoading = false; + }); + _showMessage('Berhasil Upload Image'); + } + } on PlatformException catch (e) { + print('Failed to pick image : $e'); + } + } + + Widget bottomSheet() { + return Container( + height: 100.0, + width: MediaQuery.of(context).size.width, + margin: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 20, + ), + child: Column( + children: <Widget>[ + Text( + "Choose Profile photo", + style: secondaryTextStyle.copyWith(fontSize: 20), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton.icon( + icon: const Icon(Icons.camera), + onPressed: () { + Navigator.pop(context); + takePhoto(ImageSource.camera); + }, + label: Text( + "Camera", + style: primaryTextStyle, + ), + ), + TextButton.icon( + icon: const Icon(Icons.image), + onPressed: () { + Navigator.pop(context); + takePhoto(ImageSource.gallery); + }, + label: Text("Gallery", style: primaryTextStyle), + ), + ], + ), + ], + ), + ); + } + + Widget imageProfile(String? urlImage) { + return Stack( + children: [ + CircleAvatar( + radius: getProportionateScreenWidth(40), + backgroundImage: profileImageProvider.imageFile != null + ? FileImage(profileImageProvider.imageFile!) as ImageProvider + : urlImage != null + ? NetworkImage(urlImage) as ImageProvider + : const AssetImage("assets/images/Profile Image.png") + as ImageProvider, + ), + Positioned( + bottom: 0, + right: 0, + child: Container( + width: getProportionateScreenWidth(30), + height: getProportionateScreenHeight(30), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey[200], + border: Border.all( + width: getProportionateScreenWidth(2), + color: Colors.white)), + child: IconButton( + icon: Icon( + size: getProportionateScreenHeight(10), + FeatherIcons.edit, + color: Colors.black, + ), + onPressed: () { + showModalBottomSheet( + context: context, + builder: (context) => GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + Navigator.pop(context); + }, + child: bottomSheet(), + ), + ); + }, + ), + ), + ), + ], + ); + } + + getProfile() async { + var email = await UsersInfo().getEmail(); + Provider.of<UserInfoProvider>(context, listen: false).getUserInfo(email); + } + + PageProvider pageProvider = + Provider.of<PageProvider>(context, listen: false); + + Future _showDialogLogout() { + return showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + backgroundColor: Theme.of(context).colorScheme.background, + surfaceTintColor: Colors.transparent, + contentPadding: const EdgeInsets.fromLTRB(12, 26, 22, 15), + content: Padding( + padding: const EdgeInsets.only(bottom: 15.0), + child: Text( + textAlign: TextAlign.center, + 'Apakah anda yakin ingin keluar?', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + ), + ), + ), + actions: [ + GestureDetector( + onTap: () async { + final provider = Provider.of<FirebaseAuthenticationProvider>( + context, + listen: false); + + await UsersInfo().logout(); + await provider.logout(); + Condition.loginEmail = false; + Condition.loginFirebase = false; + UsersInfo().getIdUser().then((value) { + FirebaseMessaging.instance + .unsubscribeFromTopic("payment-before-paid-$value"); + FirebaseMessaging.instance + .unsubscribeFromTopic("payment-after-paid-$value"); + FirebaseMessaging.instance + .unsubscribeFromTopic("qna-new-qna-$value"); + FirebaseMessaging.instance + .unsubscribeFromTopic("qna-reply-qna-$value"); + FirebaseMessaging.instance + .unsubscribeFromTopic("alert-carts-$value"); + }); + pageProvider.remove(); + + final themeProvider = + Provider.of<ThemeProvider>(context, listen: false); + await themeProvider.saveCurrentTheme(); + + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (_) => LoginEmail(), + ), + (_) => false, + ); + }, + child: Text('Ya', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode ,)), + ), + SizedBox(width: getProportionateScreenWidth(5)), + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Tidak', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode ,)), + ), + const SizedBox(width: 12), + ], + ), + ); + } + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.background, + centerTitle: true, + scrolledUnderElevation: 0.0, + title: Text( + "Profile", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenHeight(12), + fontWeight: semiBold, + letterSpacing: 2.3, + ), + ), + actions: [ + IconButton( + icon: SvgPicture.asset( + themeProvider.themeData == ThemeClass.darkmode + ? 'assets/icons/moon.svg' + : 'assets/icons/sun.svg', + ), + onPressed: () { + setState(() { + themeProvider.toggleTheme(); + }); + }, + ), + SizedBox(width: getProportionateScreenWidth(7)), + ], + ), + body: Stack( + children: [ + ListView( + children: [ + Container( + height: getProportionateScreenHeight(100), + // width: 360, + decoration: BoxDecoration( + color: Colors.grey[300], + image: DecorationImage( + image: AssetImage( isDarkMode + ? 'assets/images/cover_dark.png' + : 'assets/images/cover_light.png'), + fit: BoxFit.cover, + ), + ), + // child: const Padding( + // padding: EdgeInsets.all(10), + // child: Align( + // alignment: Alignment.bottomRight, + // // child: Icon( + // // Icons.camera_alt, + // // color: Colors.white, + // // ), + // ), + // ), + ), + Transform.translate( + offset: Offset(0, getProportionateScreenHeight(-40)), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Consumer<UserInfoProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return const CircleAvatar( + backgroundColor: Colors.white, + child: Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ), + ); + } else if (state.state == ResultState.HasData) { + //var idus = state.result!.data[0].idUser; + + return Column( + children: [ + imageProfile( + state.result?.data[0].fotoProfile), + SizedBox( + height: getProportionateScreenHeight(16)), + Text( + state.result!.data[0].fullname ?? ' ', + style: secondaryTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: + getProportionateScreenWidth(14)), + textAlign: TextAlign.center, + ), + SizedBox( + height: getProportionateScreenHeight(3)), + Text( + state.result!.data[0].email ?? '', + style: primaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12), + ), + textAlign: TextAlign.center, + ), + SizedBox( + height: getProportionateScreenHeight(15)), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const Certificate(), + ), + ); + }, + child: Container( + height: getProportionateScreenHeight(40), + padding: + const EdgeInsets.only(left: 10, right: 10), + // width: 10, + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: + Theme.of(context).brightness == + Brightness.dark + ? Colors.transparent + : Colors.grey, + spreadRadius: 0.01, + blurRadius: 4, + offset: + const Offset(4, 5), // Shadow position + ), + ], + ), + child: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Image.asset( + "assets/images/certificate_icon_profile.png", + width: 18, + color: Theme.of(context) + .colorScheme + .onBackground, + ), + SizedBox( + width: + getProportionateScreenWidth(15), + ), + Flexible( + child: Text( + 'Ayo cek status sertifikat mu sekarang. Download dan bagikan ke media sosial!', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: + getProportionateScreenWidth( + 12), + ), + )), + Icon( + Icons.arrow_forward_ios, + size: + getProportionateScreenWidth(20), + ) + ], + ), + ), + ) + ], + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + return Center( + child: TextButton( + child: const Column( + crossAxisAlignment: + CrossAxisAlignment.center, + children: [Icon(Icons.refresh)], + ), + onPressed: () => getProfile()), + ); + } else { + return const Center(child: Text('')); + } + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(15)), + SizedBox(height: getProportionateScreenHeight(10)), + Text( + 'Preferensi Akun', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: bold, + ), + ), + SizedBox(height: getProportionateScreenHeight(13)), + AboutAccountList( + onPress: () { + Route route = MaterialPageRoute( + builder: (context) => DataDiri(), + ); + Navigator.push( + context, + route, + ).then((value) async { + var email = await UsersInfo().getEmail(); + setState(() { + Provider.of<UserInfoProvider>(context, + listen: false) + .getUserInfo(email); + }); + }); + }, + title: 'Data Diri'), + AboutAccountList( + onPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SettingAkun(), + ), + ); + }, + title: 'Pengaturan Akun'), + AboutAccountList( + onPress: () { + pageProvider.remove(); + Provider.of< + historyProvider + .HistoryTranscationsProvider>(context, + listen: false) + .getHistoryTransaction(); + + Navigator.push(context, + CustomNavigator(child: RiwayatTransaksi())); + }, + title: 'Riwayat Transaksi'), + SizedBox(height: getProportionateScreenWidth(30)), + // Text( + // 'Bantuan Dan Dukungan', + // style: thirdTextStyle.copyWith( + // fontSize: getProportionateScreenWidth(12), + // fontWeight: bold, + // ), + // ), + // SizedBox(height: getProportionateScreenHeight(13)), + // AboutAccountList( + // onPress: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => TermsAndCondition( + // url: '$frontEndUrl/about-us', id: 'about'), + // ), + // ); + // }, + // title: 'Tentang Vocasia'), + // AboutAccountList( + // onPress: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => TermsAndCondition( + // url: '$frontEndUrl/syarat-ketentuan', + // id: 'sk'), + // ), + // ); + // }, + // title: 'Syarat dan Ketentuan'), + // AboutAccountList( + // onPress: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => TermsAndCondition( + // url: '$frontEndUrl/privacy-policy', + // id: 'prv'), + // ), + // ); + // }, + // title: 'Kebijakan Privasi'), + // AboutAccountList( + // onPress: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => TermsAndCondition( + // url: '$frontEndUrl/contact', id: 'ctc'), + // ), + // ); + // }, + // title: 'Kontak Kami'), + // AboutAccountList( + // onPress: () { + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => TermsAndCondition( + // url: '$frontEndUrl/help', id: 'help'), + // ), + // ); + // }, + // title: 'Bantuan'), + Center( + child: DefaultButton( + text: 'Keluar', + press: () { + _showDialogLogout(); + }, + ), + ) + ], + ), + ), + ) + ], + ), + ], + ), + ); + } +} diff --git a/lib/screens/profile/components/about_profile_list.dart b/lib/screens/profile/components/about_profile_list.dart new file mode 100644 index 0000000..d34133e --- /dev/null +++ b/lib/screens/profile/components/about_profile_list.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class AboutAccountList extends StatelessWidget { + const AboutAccountList({Key? key, required this.onPress, required this.title}) + : super(key: key); + final GestureTapCallback onPress; + final String title; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onPress, + child: Center( + child: Container( + // color: primaryColor, + margin: EdgeInsets.only(bottom: getProportionateScreenWidth(26)), + child: Row( + children: [ + Expanded( + child: Text( + title, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.5), + ), + ), + Icon( + Icons.keyboard_arrow_right, + size: getProportionateScreenWidth(24), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/profile/profile_page.dart b/lib/screens/profile/profile_page.dart new file mode 100644 index 0000000..f3ca307 --- /dev/null +++ b/lib/screens/profile/profile_page.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/sign_in_screen.dart'; +import 'package:initial_folder/screens/profile/account_not_sign_in/not_sign_in_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; + +// class ProfilePage extends StatelessWidget { +// ProfilePage({Key? key}) : super(key: key); + +// final _pageList = [ +// SignInScreenEmail(), +// SignInScreenGoogle(), +// NotSignInScreen(), +// ]; +// @override +// Widget build(BuildContext context) { +// ProfileProvider profileProvider = Provider.of<ProfileProvider>(context); +// void profpage() async { +// var token = await UsersInfo().getToken(); +// FirebaseAuth.instance.authStateChanges().listen((User? user) { +// if (token != null || user != null) { +// if (token != null) { +// profileProvider.currentIndex = 0; +// } else if (user != null) { +// profileProvider.currentIndex = 1; +// } +// } else { +// profileProvider.currentIndex = 2; +// } +// }); +// } + +// Widget body() { +// switch (profileProvider.currentIndex) { +// case 0: +// return SignInScreenEmail(); +// case 1: +// return SignInScreenGoogle(); +// case 2: +// return NotSignInScreen(); +// default: +// return SignInScreenEmail(); +// } +// } + +// profpage(); +// // return Scaffold(body: body()); +// return IndexedStack( +// index: profileProvider.currentIndex, +// children: _pageList, +// ); +// } +// } + +class ProfilePage extends StatelessWidget { + const ProfilePage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: (Condition.loginEmail || Condition.loginFirebase) + ? SignInScreen() + : NotSignInScreen()); + } +} diff --git a/lib/screens/registrasi/components/get_user_data.dart b/lib/screens/registrasi/components/get_user_data.dart new file mode 100644 index 0000000..534fa33 --- /dev/null +++ b/lib/screens/registrasi/components/get_user_data.dart @@ -0,0 +1,16 @@ +import 'package:flutter/widgets.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/carts_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +import 'package:initial_folder/providers/whislist_provider.dart'; +import 'package:provider/provider.dart'; + +getUserData(BuildContext context) async { + var email = await UsersInfo().getEmail(); + await Provider.of<UserInfoProvider>(context, listen: false) + .getUserInfo(email); + await Provider.of<CartsProvider>(context, listen: false).getCarts(); + await Provider.of<WishlistProvider>(context, listen: false).getWishlist(); + await Provider.of<MyCourseProvider>(context, listen: false).getMyCourse(); +} diff --git a/lib/screens/registrasi/registrasi_screen.dart b/lib/screens/registrasi/registrasi_screen.dart new file mode 100644 index 0000000..c89b06c --- /dev/null +++ b/lib/screens/registrasi/registrasi_screen.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/registrasi_google_provider.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_facebook/registrasi_facebook_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_google/registrasi_google_screen.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/default_icon_button.dart'; +import 'package:initial_folder/widgets/login_regist/footer.dart'; +import 'package:initial_folder/widgets/login_regist/header.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; + +class RegistrationScreen extends StatefulWidget { + const RegistrationScreen({Key? key}) : super(key: key); + static String routeName = "/registration"; + + @override + State<RegistrationScreen> createState() => _RegistrationScreenState(); +} + +class _RegistrationScreenState extends State<RegistrationScreen> { + bool isLoadingGoogle = false; + bool isLoadingFacebook = false; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Column(children: [ + Container( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(12), + top: getProportionateScreenHeight(8)), + alignment: Alignment.topLeft, + child: TextButton( + child: Text('Daftar nanti', + style: primaryTextStyle.copyWith( + color: primaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14))), + onPressed: () { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, (Route<dynamic> route) => false); + })), + SizedBox(height: getProportionateScreenHeight(35)), + Container( + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Header( + jarak: 16, + text: + 'Yuk Siapkan Dirimu, Mulai Upgrade Skill Bersama Vocasia', + style: secondaryTextStyle.copyWith( + color: tenthColor, + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: getProportionateScreenHeight(32)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15.0)), + child: isLoadingGoogle + ? LoadingButton( + backgroundButtonColor: tenthColor, + textButtonColor: ninthColor) + : DefaultIconButton( + text: "Lanjutkan dengan Google", + press: () async { + setState(() { + isLoadingGoogle = true; + }); + await RegistrationGoogle() + .handleRegistrationGoogle(context); + final userData = + Provider.of<RegistrasiGoogleProvider>(context, + listen: false); + print(userData.name); + print(userData.email); + }, + icon: 'assets/icons/google.png', + iconWidth: 25, + iconHeight: 25, + ), + ), + // SizedBox(height: getProportionateScreenWidth(16)), + // Padding( + // padding: EdgeInsets.symmetric( + // horizontal: getProportionateScreenWidth(15.0)), + // child: isLoadingFacebook + // ? LoadingButton( + // backgroundButtonColor: tenthColor, + // textButtonColor: ninthColor) + // : DefaultIconButton( + // text: "Lanjutkan dengan Facebook", + // press: () { + // setState(() { + // isLoadingFacebook = true; + // }); + // RegistrationFacebook() + // .handleRegistrationFacebook(context); + // }, + // icon: 'assets/icons/facebook.png', + // iconWidth: 25, + // iconHeight: 25, + // ), + // ), + SizedBox(height: getProportionateScreenWidth(16)), + Padding( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(15.0)), + child: DefaultButton( + text: "Daftar dengan Email", + press: () { + Navigator.pushNamed( + context, + RegistrationEmail.routeName, + arguments: { + 'email': '', + 'nama': '', + }, + ); + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(32)), + Footer( + textOne: 'Sudah punya akun? ', + textTwo: 'Masuk', + route: LoginEmail.routeName, + ) + ], + ), + ), + // Padding( + // padding: EdgeInsets.symmetric(horizontal: 50), + // child: Text( + // 'Dengan membuat akun, anda menyetujui Kebijakan dan Persyaratan Privasi', + // maxLines: 2, + // textAlign: TextAlign.center, + // style: TextStyle( + // height: 2, + // fontSize: getProportionateScreenWidth(14), + // fontWeight: FontWeight.w400, + // color: Colors.white)), + // ), + ]), + )), + ); + } +} diff --git a/lib/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart b/lib/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart new file mode 100644 index 0000000..a541232 --- /dev/null +++ b/lib/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart @@ -0,0 +1,326 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/providers/registrasi_google_provider.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_email/success_regis_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/auth_provider.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/custom_text_form_field.dart'; +import 'package:initial_folder/widgets/login_regist/default_button.dart'; +import 'package:initial_folder/widgets/login_regist/footer.dart'; +import 'package:initial_folder/widgets/login_regist/header.dart'; +import 'package:initial_folder/widgets/login_regist/loading_button.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/size_config.dart'; + +import '../../../widgets/login_regist/failed_login.dart'; +import '../../login/components/get_user_data.dart'; + +class RegistrationEmail extends StatefulWidget { + static String routeName = "/registration_email"; + RegistrationEmail({ + Key? key, + }) : super(key: key); + + @override + _RegistrationEmailState createState() => _RegistrationEmailState(); +} + +class _RegistrationEmailState extends State<RegistrationEmail> { + final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); + final TextEditingController nameController = TextEditingController(text: ''); + + final TextEditingController emailController = TextEditingController(text: ''); + final TextEditingController reEmailController = + TextEditingController(text: ''); + + final TextEditingController phoneNumberController = + TextEditingController(text: ''); + + final TextEditingController passwordController = + TextEditingController(text: ''); + final TextEditingController rePasswordController = + TextEditingController(text: ''); + bool isLoading = false; + bool failed = false; + bool _isObscure = true; + bool _isObscure1 = true; + + @override + Widget build(BuildContext context) { + SizeConfig().init(context); + AuthProvider authProvider = Provider.of<AuthProvider>(context); + + final userProvider = Provider.of<RegistrasiGoogleProvider>(context); + final Map<String, dynamic>? args = + ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>?; + nameController.text = nameController.text.isNotEmpty + ? nameController.text + : (args?['nama'] ?? userProvider.name ?? ''); + emailController.text = emailController.text.isNotEmpty + ? emailController.text + : (args?['email'] ?? userProvider.email ?? ''); + + handleSignUp() async { + setState(() { + isLoading = true; + }); + + try { + if (phoneNumberController.text.length > 16) { + throw ('Failed\n\nNomor HP maksimal hanya 15 angka'); + } + if (await authProvider.register( + name: nameController.text, + email: emailController.text, + password: passwordController.text, + phoneNumber: phoneNumberController.text, + )) { + await UsersInfo().setEmail(emailController.text); + await authProvider + .login( + email: emailController.text, + password: passwordController.text, + ) + .then((value) async { + Condition.loginEmail = true; + await getUserData(context); + Navigator.of(context).pushNamedAndRemoveUntil( + RegisSuccess.routeName, (Route<dynamic> route) => false); + }); + } + } catch (e) { + showDialog( + context: context, + builder: (context) => AlertDialog( + content: Container( + height: 90, + alignment: AlignmentDirectional.topStart, + child: Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(15), + right: getProportionateScreenHeight(10)), + child: Text( + e.toString(), + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(11), + letterSpacing: 1, + ), + ), + ), + ), + actions: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenHeight(15), + bottom: getProportionateScreenHeight(2)), + child: Text( + 'Ok', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor, + ), + ), + ), + ), + ], + ), + ); + } + + setState(() { + isLoading = false; + }); + } + + void _validateInputs() { + if (this._formKey.currentState!.validate()) { + handleSignUp(); + } + } + + Widget form() { + Widget nameInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: nameController, + hinttext: 'Masukan nama lengkap', + title: 'Nama Lengkap', + validate: validateName, + ); + } + + Widget emailInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: emailController, + hinttext: 'Masukan email', + title: 'Email', + validate: validateEmail, + ); + } + + Widget reEmailInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: reEmailController, + hinttext: 'Masukan konfirmasi email', + title: 'Konfirmasi Email', + validate: (String? value) { + return validateReEmail(value, emailController.text); + }, + ); + } + + Widget phoneNumberInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: phoneNumberController, + hinttext: 'Masukan no hp', + title: 'No Telepon', + validate: validatePhone, + ); + } + + Widget passwordInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: passwordController, + hinttext: 'Masukan password', + title: 'Password', + obscuretext: _isObscure, + validate: validatePassword, + suffix: GestureDetector( + onTap: () => setState(() { + _isObscure = !_isObscure; + }), + child: _isObscure + ? Icon( + FeatherIcons.eyeOff, + color: secondaryColor, + size: 18, + ) + : Icon( + FeatherIcons.eye, + color: secondaryColor, + size: 18, + )), + ); + } + + Widget rePasswordInput() { + return CustomTextField( + pad: getProportionateScreenWidth(16), + controler: rePasswordController, + hinttext: 'Masukan password', + title: 'Konfirmasi Password', + obscuretext: _isObscure1, + suffix: GestureDetector( + onTap: () => setState(() { + _isObscure1 = !_isObscure1; + }), + child: _isObscure1 + ? Icon( + FeatherIcons.eyeOff, + color: secondaryColor, + size: 18, + ) + : Icon( + FeatherIcons.eye, + color: secondaryColor, + size: 18, + )), + validate: (String? value) { + return validateRePassword(value, passwordController.text); + }, + ); + } + + Widget button() { + return Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(15), + right: getProportionateScreenWidth(15), + top: getProportionateScreenHeight(3)), + child: isLoading + ? LoadingButton( + backgroundButtonColor: primaryColor, + textButtonColor: baruTextutih, + ) + : DefaultButton( + text: 'Register', + press: _validateInputs, + ), + ); + } + + return Form( + key: _formKey, + child: Column( + children: [ + nameInput(), + emailInput(), + reEmailInput(), + phoneNumberInput(), + passwordInput(), + rePasswordInput(), + SizedBox( + height: getProportionateScreenHeight(8), + ), + button(), + ], + ), + ); + } + + return SafeArea( + child: Scaffold( + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: getProportionateScreenHeight(25), + ), + Container( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(25), + left: getProportionateScreenWidth(25)), + child: Align( + alignment: Alignment.center, + child: Text('Register', + style: thirdTextStyle.copyWith( + // color: baruTexthitam, + fontWeight: FontWeight.w900, + fontSize: getProportionateScreenWidth(20), + letterSpacing: 0.5))), + ), + SizedBox( + height: getProportionateScreenHeight(32), + ), + form(), + SizedBox(height: getProportionateScreenHeight(32)), + Footer( + textOne: 'Sudah punya akun? ', + textTwo: 'Log in', + route: LoginEmail.routeName, + height: 14, + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/screens/registrasi/registrasi_with_email/success_regis_screen.dart b/lib/screens/registrasi/registrasi_with_email/success_regis_screen.dart new file mode 100644 index 0000000..ce01478 --- /dev/null +++ b/lib/screens/registrasi/registrasi_with_email/success_regis_screen.dart @@ -0,0 +1,72 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/registrasi/components/get_user_data.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/login_regist/header.dart'; +import 'package:flutter/gestures.dart'; + +class RegisSuccess extends StatelessWidget { + static String routeName = "/regis_success"; + RegisSuccess({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + SizeConfig().init(context); + return SafeArea( + child: Scaffold( + body: Center( + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + Icon( + Icons.check_rounded, + color: Colors.greenAccent[700], + size: getProportionateScreenWidth(60), + ), + SizedBox(height: getProportionateScreenHeight(24)), + Header( + title: false, + text2: "Registrasi Akun Sukses", + text: + 'Kami telah mengirimkan notifikasi registrasi akun ke email anda', + style: primaryTextStyle.copyWith( + color: Theme.of(context).colorScheme.onBackground, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ), + ), + SizedBox(height: getProportionateScreenHeight(44)), + Container( + // padding: EdgeInsets.only( + // left: getProportionateScreenWidth(5), + // top: getProportionateScreenWidth(5)), + alignment: Alignment.center, + child: RichText( + text: TextSpan( + children: <TextSpan>[ + TextSpan( + text: 'Lanjut ke Halaman Beranda', + style: primaryTextStyle.copyWith( + color: primaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(18), + ), + recognizer: TapGestureRecognizer() + ..onTap = () async { + await getUserData(context); + + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, + (Route<dynamic> route) => false); + print('masuk'); + }), + ], + ), + ), + ), + ]), + ), + ), + ); + } +} diff --git a/lib/screens/registrasi/registrasi_with_facebook/registrasi_facebook_screen.dart b/lib/screens/registrasi/registrasi_with_facebook/registrasi_facebook_screen.dart new file mode 100644 index 0000000..2363915 --- /dev/null +++ b/lib/screens/registrasi/registrasi_with_facebook/registrasi_facebook_screen.dart @@ -0,0 +1,76 @@ +// import 'package:flutter/material.dart'; +// import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +// import 'package:initial_folder/providers/incomplete_profile_provider.dart'; +// import 'package:initial_folder/providers/login_provider.dart'; +// import 'package:initial_folder/screens/home/home_screen.dart'; +// import 'package:initial_folder/screens/login/components/get_user_data.dart'; +// import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +// import 'package:initial_folder/screens/profile/account_sign_in/incomplete_profile_screen.dart'; +// import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +// import 'package:provider/provider.dart'; + +// import '../../../size_config.dart'; +// import '../../../theme.dart'; + +// class RegistrationFacebook { +// Future handleRegistrationFacebook(BuildContext context) async { +// final provider = +// Provider.of<FirebaseAuthenticationProvider>(context, listen: false); +// if (await provider.facebookLogin()) { +// await getUserData(context); + +// Condition.loginFirebase = true; + +// IncompleteProfileProvider userInfoIncomplete = +// Provider.of<IncompleteProfileProvider>(context, listen: false); +// await userInfoIncomplete.getUserInfoIncomplete(); +// if (userInfoIncomplete.isUserInfoComplete == null) { +// Navigator.of(context).pushNamedAndRemoveUntil( +// LoginEmail.routeName, (Route<dynamic> route) => false); +// return; +// } + +// if (userInfoIncomplete.isUserInfoComplete!) { +// Navigator.of(context).pushNamedAndRemoveUntil( +// HomeScreen.routeName, (Route<dynamic> route) => false); +// } else { +// Navigator.of(context).pushNamedAndRemoveUntil( +// IncompleteProfile.routeName, (Route<dynamic> route) => false); +// } +// } else { +// showDialog( +// context: context, +// builder: (context) => AlertDialog( +// insetPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 20), +// content: Container( +// alignment: AlignmentDirectional.bottomCenter, +// height: 50, +// child: Text( +// 'Ada kendala dalam login, coba lagi atau periksa akun anda', +// style: primaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(12), letterSpacing: 1), +// ), +// ), +// actions: [ +// Container( +// height: 30, +// alignment: AlignmentDirectional.topEnd, +// child: GestureDetector( +// onTap: () { +// Navigator.of(context).pop(); +// }, +// child: Text('Tutup', +// style: primaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(12), +// letterSpacing: 1, +// color: primaryColor)), +// ), +// ), +// ], +// ), +// ); +// } +// Provider.of<LoginProvider>(context, listen: false) +// .loadFacebookActive(false); +// } +// } diff --git a/lib/screens/registrasi/registrasi_with_google/registrasi_google_screen.dart b/lib/screens/registrasi/registrasi_with_google/registrasi_google_screen.dart new file mode 100644 index 0000000..451121a --- /dev/null +++ b/lib/screens/registrasi/registrasi_with_google/registrasi_google_screen.dart @@ -0,0 +1,145 @@ +import 'package:flutter/material.dart'; +import 'package:google_sign_in/google_sign_in.dart'; +import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +import 'package:initial_folder/providers/incomplete_profile_provider.dart'; +import 'package:initial_folder/providers/registrasi_google_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/components/get_user_data.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/incomplete_profile_screen.dart'; +import 'package:initial_folder/screens/registrasi/registrasi_with_email/registrasi_email_screen.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class RegistrationGoogle { + Future handleRegistrationGoogle(BuildContext context) async { + final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn(); + final googleAuth = await googleUser?.authentication; + final name = googleUser?.displayName; + final email = googleUser?.email; + final provider = + Provider.of<FirebaseAuthenticationProvider>(context, listen: false); + if (await provider.googleLogin()) { + await getUserData(context); + + Condition.loginFirebase = true; + + IncompleteProfileProvider userInfoIncomplete = + Provider.of<IncompleteProfileProvider>(context, listen: false); + + // [!CHECKPOINT!]: + await userInfoIncomplete.getUserInfoIncomplete(); + if (userInfoIncomplete.isUserInfoComplete == null) { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + return; + } + + if (userInfoIncomplete.isUserInfoComplete!) { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, (Route<dynamic> route) => false); + } else { + Navigator.of(context).pushNamedAndRemoveUntil( + IncompleteProfile.routeName, (Route<dynamic> route) => false); + } + } else if (googleAuth != null && name != null && email != null) { + // data ditemukan, gunakan di sini + Provider.of<RegistrasiGoogleProvider>(context, listen: false) + .setNameAndEmail(name, email); + + Navigator.pushReplacementNamed(context, RegistrationEmail.routeName); + } else { + showDialog( + context: context, + builder: (context) => AlertDialog( + insetPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 20), + content: Container( + alignment: AlignmentDirectional.bottomCenter, + height: 50, + child: Text( + 'Ada kendala dalam mendaftar, coba lagi atau periksa akun anda', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + ), + ), + actions: [ + Container( + height: 30, + alignment: AlignmentDirectional.topEnd, + child: GestureDetector( + onTap: () { + Navigator.of(context).pop(); + }, + child: Text('Tutup', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 1, + color: primaryColor)), + ), + ), + ], + ), + ); + } + } + + // Future handleRegistrationGoogle(BuildContext context) async { + // final provider = + // Provider.of<FirebaseAuthenticationProvider>(context, listen: false); + // if (await provider.googleLogin()) { + // await getUserData(context); + + // Condition.loginFirebase = true; + + // IncompleteProfileProvider userInfoIncomplete = Provider.of<IncompleteProfileProvider>(context, listen: false); + // await userInfoIncomplete.getUserInfoIncomplete(); + // if (userInfoIncomplete.isUserInfoComplete) { + // Navigator.of(context) + // .pushNamedAndRemoveUntil( + // HomeScreen.routeName, + // (Route<dynamic> route) => false); + // } else { + // Navigator.of(context) + // .pushNamedAndRemoveUntil( + // IncompleteProfile.routeName, + // (Route<dynamic> route) => false); + // } + // } else { + // showDialog( + // context: context, + // builder: (context) => AlertDialog( + // insetPadding: EdgeInsets.symmetric(horizontal: 25, vertical: 20), + // content: Container( + // alignment: AlignmentDirectional.bottomCenter, + // height: 50, + // child: Text( + // 'Ada kendala dalam mendaftar, coba lagi atau periksa akun anda', + // style: primaryTextStyle.copyWith( + // fontSize: getProportionateScreenWidth(12), letterSpacing: 1), + // ), + // ), + // actions: [ + // Container( + // height: 30, + // alignment: AlignmentDirectional.topEnd, + // child: GestureDetector( + // onTap: () { + // Navigator.of(context).pop(); + // }, + // child: Text('Tutup', + // style: primaryTextStyle.copyWith( + // fontSize: getProportionateScreenWidth(12), + // letterSpacing: 1, + // color: primaryColor)), + // ), + // ), + // ], + // ), + // ); + // } + // } +} diff --git a/lib/screens/search_course/component/filter.dart b/lib/screens/search_course/component/filter.dart new file mode 100644 index 0000000..b797c2f --- /dev/null +++ b/lib/screens/search_course/component/filter.dart @@ -0,0 +1,462 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator_pop.dart'; +import 'package:initial_folder/widgets/search_and_filter_course.dart'; +import 'package:provider/provider.dart'; +import 'package:initial_folder/providers/filters_course_provider.dart' + as filterCourseProv; +import 'package:initial_folder/providers/search_provider.dart' + as searchProvider; + +class Filter extends StatelessWidget { + const Filter({ + Key? key, + this.isNotSearch, + this.onApplyFilter, + }) : super(key: key); + + final bool? isNotSearch; + final VoidCallback? onApplyFilter; + + @override + Widget build(BuildContext context) { + filterCourseProv.FilterCourseProvider filterCourseProvider = + Provider.of<filterCourseProv.FilterCourseProvider>(context); + + final listChoices = <ItemChoiceFilter>[ + ItemChoiceFilter('', '0', 'Semua '), + ItemChoiceFilter('5', '1', '5'), + ItemChoiceFilter('4', '1', '4'), + ItemChoiceFilter('3', '1', '3'), + ItemChoiceFilter('2', '1', '2'), + ItemChoiceFilter('1', '1', '1'), + ]; + + Widget categoriCheckBox(String id, String title, [Function? onChange]) { + return Container( + margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)), + child: Row( + children: [ + SizedBox( + width: 24, + height: 24, + child: Checkbox( + value: + filterCourseProvider.categories.contains(id) ? true : false, + onChanged: id == '' + ? (c) { + filterCourseProvider.allCategories(); + filterCourseProvider.addCategory(id); + } + : (c) { + if (filterCourseProvider.categories.contains(id)) { + filterCourseProvider.removeCategory(id); + } else { + filterCourseProvider.removeCategory(''); + + filterCourseProvider.addCategory(id); + } + }, + activeColor: Theme.of(context).brightness == Brightness.dark + ? thirteenColor + : twelveColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + side: + BorderSide(color: Theme.of(context).colorScheme.onPrimary), + ), + ), + SizedBox(width: getProportionateScreenWidth(12)), + Expanded( + child: GestureDetector( + onTap: id == '' + ? () { + filterCourseProvider.allCategories(); + filterCourseProvider.addCategory(id); + } + : () { + if (filterCourseProvider.categories.contains(id)) { + filterCourseProvider.removeCategory(id); + } else { + filterCourseProvider.removeCategory(''); + filterCourseProvider.addCategory(id); + } + }, + child: Text( + title, + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.5), + ), + ), + ), + ], + ), + ); + } + + Widget levelCheckBox(String name, String title) { + return Container( + margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)), + child: Row( + children: [ + SizedBox( + width: 24, + height: 24, + child: Checkbox( + checkColor: Theme.of(context).colorScheme.background, + value: + filterCourseProvider.levels.contains(name) ? true : false, + onChanged: name == '' + ? (c) { + filterCourseProvider.allLevel(); + filterCourseProvider.addLevel(name); + } + : (c) { + if (filterCourseProvider.levels.contains(name)) { + filterCourseProvider.removeLevel(name); + } else { + filterCourseProvider.removeLevel(''); + + filterCourseProvider.addLevel(name); + } + }, + activeColor: primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + side: + BorderSide(color: Theme.of(context).colorScheme.onPrimary), + ), + ), + SizedBox(width: getProportionateScreenWidth(12)), + Expanded( + child: GestureDetector( + onTap: name == '' + ? () { + filterCourseProvider.allLevel(); + filterCourseProvider.addLevel(name); + } + : () { + // filterCourseProvider.currentIndex = id; + if (filterCourseProvider.levels.contains(name)) { + filterCourseProvider.removeLevel(name); + } else { + filterCourseProvider.removeLevel(''); + + filterCourseProvider.addLevel(name); + } + }, + child: Text( + title, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.5), + ), + ), + ), + ], + ), + ); + } + + Widget radioPrice( + String val, + String title, + ) { + return Container( + margin: EdgeInsets.only(bottom: getProportionateScreenWidth(8)), + child: Row( + children: [ + SizedBox( + width: 24, + height: 24, + child: Theme( + data: ThemeData( + unselectedWidgetColor: fourthColor, + ), + child: Radio( + activeColor: primaryColor, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: val, + groupValue: filterCourseProvider.currentIndexPrice, + onChanged: (c) { + filterCourseProvider.currentIndexPrice = val; + }), + ), + ), + SizedBox( + width: getProportionateScreenWidth(12), + ), + Expanded( + child: GestureDetector( + onTap: () { + filterCourseProvider.currentIndexPrice = val; + }, + child: Text( + title, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.5, + ), + ), + ), + ), + ], + ), + ); + } + + final provSearch = + Provider.of<searchProvider.SearchProvider>(context, listen: false); + + Widget bottomNav() { + return Container( + width: double.infinity, + height: getProportionateScreenHeight(60), + decoration: BoxDecoration( + boxShadow: [ + BoxShadow( + offset: Offset(0, -2), + color: Theme.of(context).colorScheme.primaryContainer, + ) + ], + ), + child: Center( + child: ElevatedButton( + style: ElevatedButton.styleFrom( + minimumSize: Size(getProportionateScreenWidth(110), + getProportionateScreenHeight(33)), + backgroundColor: primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15)), + ), + onPressed: () { + provSearch.searchText = ""; + + Provider.of<filterCourseProv.FilterCourseProvider>(context, + listen: false) + .isSearchsTrue(); + filterCourseProvider.filter( + category: filterCourseProvider.categories.join(','), + price: filterCourseProvider.currentIndexPrice, + level: filterCourseProvider.levels.join(','), + rating: filterCourseProvider.currentIndexRating, + keyword: Provider.of<searchProvider.SearchProvider>(context, + listen: false) + .searchTextFilter, + ); + + onApplyFilter!(); + + isNotSearch == null + ? Navigator.pop(context) + : Navigator.pushReplacement( + context, + CustomNavigatorPop( + child: SearchAndFilterCourse(), + ), + ); + }, + child: Text( + 'Filter', + style: thirdTextStyle.copyWith(color: baruTextutih), + ), + ), + ), + ); + } + + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + appBar: AppBar( + backgroundColor: Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + leading: IconButton( + icon: Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + actions: [ + TextButton( + onPressed: () { + filterCourseProvider.resetFilter(); + }, + child: Text( + 'Reset', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: primaryColor, + ), + ), + ) + ], + ), + body: Container( + margin: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: ListView( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : secondaryColor.withOpacity(0.3), + ), + height: 40, + child: Consumer<searchProvider.SearchProvider>( + builder: (context, state, _) => TextField( + autofocus: false, + onSubmitted: (value) { + filterCourseProvider.isSearchsFalse(); + + state.searchTextFilter = validatorSearchFilter(value); + state.initSearchCourse( + price: filterCourseProvider.currentIndexPrice, + level: filterCourseProvider.levels.join(','), + rating: filterCourseProvider.currentIndexRating, + ); + }, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ), + onChanged: (value) { + state.searchTextFilter = validatorSearchFilter(value); + }, + cursorColor: secondaryColor, + decoration: InputDecoration( + border: InputBorder.none, + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: sevenColor), + borderRadius: BorderRadius.circular(10)), + contentPadding: + EdgeInsets.only(top: getProportionateScreenHeight(2)), + prefixIcon: Icon( + FeatherIcons.search, + size: 20, + color: primaryColor, + ), + hintText: 'Cari Kursus', + hintStyle: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: secondaryColor, + letterSpacing: 0.5, + ), + ), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(20)), + Text( + 'Harga', + style: + thirdTextStyle.copyWith(letterSpacing: 1, fontWeight: bold), + ), + SizedBox(height: getProportionateScreenWidth(8)), + radioPrice('', 'Semua'), + radioPrice('1', 'Gratis'), + radioPrice('0', 'Berbayar'), + Text( + 'Tingkat', + style: + thirdTextStyle.copyWith(letterSpacing: 1, fontWeight: bold), + ), + SizedBox(height: getProportionateScreenWidth(8)), + levelCheckBox('', 'Semua'), + levelCheckBox('beginner', 'Pemula'), + levelCheckBox('intermediate', 'Menengah'), + levelCheckBox('high', 'Ahli'), + Text( + 'Rating', + style: + thirdTextStyle.copyWith(letterSpacing: 1, fontWeight: bold), + ), + SizedBox(height: getProportionateScreenWidth(8)), + Wrap( + spacing: 5, + runSpacing: 5, + children: listChoices + .map( + (e) => ChoiceChip( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30)), + showCheckmark: false, + materialTapTargetSize: MaterialTapTargetSize.padded, + backgroundColor: + Theme.of(context).colorScheme.primaryContainer, + side: BorderSide( + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : primaryColor, + ), + labelPadding: const EdgeInsets.symmetric(horizontal: 6), + label: Row( + mainAxisSize: MainAxisSize.min, + children: [ + (e.icon == '0') + ? const Text('') + : FaIcon( + FontAwesomeIcons.solidStar, + color: thirteenColor, + size: getProportionateScreenWidth(11), + ), + const SizedBox( + width: 4, + ), + Text( + e.label, + style: primaryTextStyle.copyWith( + color: filterCourseProvider.currentIndexRating == + e.id.toString() + ? Theme.of(context).colorScheme.background + : Theme.of(context).colorScheme.onPrimary, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12), + fontWeight: reguler, + ), + ), + ], + ), + selected: filterCourseProvider.currentIndexRating == + e.id.toString(), + selectedColor: + Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : primaryColor, + onSelected: (_) { + filterCourseProvider.currentIndexRating = + e.id.toString(); + }, + elevation: 1, + ), + ) + .toList(), + ), + SizedBox( + height: getProportionateScreenWidth(16), + ), + ], + ), + ), + bottomNavigationBar: bottomNav(), + ); + } +} + +class ItemChoiceFilter { + final String id; + final String icon; + final String label; + + ItemChoiceFilter(this.id, this.icon, this.label); +} diff --git a/lib/screens/search_course/component/list_category_icon.dart b/lib/screens/search_course/component/list_category_icon.dart new file mode 100644 index 0000000..9a0eae2 --- /dev/null +++ b/lib/screens/search_course/component/list_category_icon.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +import '../../../size_config.dart'; +import '../../../theme.dart'; + +class ListCategoryIcon extends StatelessWidget { + const ListCategoryIcon({ + Key? key, + required this.title, + required this.iconFa, + required this.onTap, + }) : super(key: key); + final String title; + final Icon iconFa; + final Function()? onTap; + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(21), + right: getProportionateScreenWidth(17), + bottom: getProportionateScreenWidth(11)), + child: InkWell( + onTap: onTap, + child: Row( + children: [ + iconFa, + SizedBox( + width: getProportionateScreenWidth(10), + ), + Expanded( + child: Html( + data: title, + style: { + "body": Style( + margin: Margins.zero, + padding: + HtmlPaddings.only(left: getProportionateScreenWidth(6)), + fontSize: FontSize(getProportionateScreenWidth(13)), + fontWeight: reguler, + letterSpacing: 0.5, + fontFamily: 'Poppins', + ), + }, + ), + ), + Icon( + Icons.keyboard_arrow_right, + size: 22, + ), + SizedBox(height: getProportionateScreenHeight(25)), + ], + ), + ), + ); + } +} diff --git a/lib/screens/search_course/search_page.dart b/lib/screens/search_course/search_page.dart new file mode 100644 index 0000000..f1e0784 --- /dev/null +++ b/lib/screens/search_course/search_page.dart @@ -0,0 +1,335 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/categories_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/home/components/body_comp/course_by_category.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/login_regist/custom_font_awesome.dart'; +import 'package:initial_folder/widgets/search_and_filter_course.dart'; +import 'package:provider/provider.dart'; +import 'component/list_category_icon.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:initial_folder/providers/search_provider.dart' as searchProv; +import 'package:initial_folder/providers/filters_course_provider.dart' + as filterProv; + +class SearchPage extends StatefulWidget { + SearchPage({Key? key}) : super(key: key); + + @override + State<SearchPage> createState() => _SearchPageState(); +} + +class _SearchPageState extends State<SearchPage> { + final GlobalKey<NavigatorState> searchKey = GlobalKey<NavigatorState>(); + bool _showShimmer = false; + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + CategoriesProvider categoriesProvider = + Provider.of<CategoriesProvider>(context); + + return WillPopScope( + onWillPop: () async { + if (searchKey.currentState!.canPop()) { + searchKey.currentState!.pop(); + return false; + } + return true; + }, + child: Navigator( + key: searchKey, + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + builder: (BuildContext context) { + if (settings.name == '/courseByCategory') { + return CourseByCategory( + name: categoriesProvider.nameCategories[0] ?? '', + categoryId: categoriesProvider.ids[0] ?? '', + subId: '', + ); + } + return SafeArea( + child: Scaffold( + backgroundColor: + Theme.of(context).brightness == Brightness.dark + ? twelveColor + : baruTextutih, + body: RefreshIndicator( + onRefresh: () async { + await categoriesProvider.getAllCategories(); + }, + child: SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: Container( + margin: EdgeInsets.symmetric(vertical: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + ), + child: GestureDetector( + onTap: () { + Provider.of<searchProv.SearchProvider>( + context, + listen: false) + .resetState(); + Provider.of< + filterProv + .FilterCourseProvider>( + context, + listen: false) + .resetState(); + Provider.of< + filterProv + .FilterCourseProvider>( + context, + listen: false) + .resetFilter(); + Navigator.push( + context, + CustomNavigator( + child: SearchAndFilterCourse(), + ), + ); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(10), + color: Theme.of(context).brightness == + Brightness.dark + ? seventeenColor + : secondaryColor.withOpacity(0.3), + ), + height: 40, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 13), + child: Icon( + FeatherIcons.search, + size: 20, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ), + SizedBox( + width: + getProportionateScreenWidth( + 10)), + Text( + 'Cari kursus..', + style: + secondaryTextStyle.copyWith( + fontSize: 12, + color: Theme.of(context) + .brightness == + Brightness.dark + ? baruTextutih + .withOpacity(0.5) + : Colors.black + .withOpacity(0.5), + ), + ) + ], + ), + ), + ), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(16)), + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16)), + child: Text( + 'Cari Berdasarkan Kategori', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(16), + ), + ), + ), + SizedBox(height: getProportionateScreenWidth(12)), + Container( + child: Consumer<CategoriesProvider>( + builder: (context, state, _) { + if (state.state == ResultState.Loading) { + return Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + children: List.generate( + 9, + (index) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 25.0), + child: Row( + children: [ + Container( + width: 23, + height: 23, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular(20), + ), + ), + SizedBox(width: 15), + Expanded( + child: Container( + height: 10, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular( + 5), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } else if (state.state == ResultState.HasData) { + var categori = state.result; + + return Column( + children: categori + .map( + (e) => ListCategoryIcon( + title: e.nameCategory ?? '', + iconFa: Icon( + fontAwesomeIconsFromString( + 'FontAwesomeIcons.${iconCategory(e.fontAwesomeClass ?? '')}'), + size: getProportionateScreenWidth( + 20), + ), + onTap: () { + Navigator.push( + context, + CustomNavigator( + child: CourseByCategory( + name: e.nameCategory ?? '', + categoryId: e.id ?? '', + subId: '', + ), + ), + ); + }, + ), + ) + .toList(), + ); + } else if (state.state == ResultState.NoData) { + return Center(child: Text(state.message)); + } else if (state.state == ResultState.Error) { + if (!_showShimmer) { + Future.delayed(Duration(seconds: 5), () { + if (mounted) { + setState(() { + _showShimmer = true; + }); + } + }); + return Shimmer.fromColors( + baseColor: Colors.white, + highlightColor: Colors.grey, + child: Column( + mainAxisAlignment: + MainAxisAlignment.start, + children: List.generate( + 9, + (index) => Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 25.0), + child: Row( + children: [ + Container( + width: 23, + height: 23, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular( + 20), + ), + ), + SizedBox(width: 15), + Expanded( + child: Container( + height: 10, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.circular( + 5), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } else { + return Center( + child: Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight( + 70)), + child: Text( + "Internet lemah, mohon muat ulang", + ), + ), + ); + } + } else { + return Center( + child: Text( + 'Terjadi Kesalahan ', + style: primaryTextStyle, + ), + ); + } + }), + ), + ], + ), + ), + ), + ), + ), + ); + }, + ); + }, + ), + ); + } +} + +class Ic { + String name; + String tile; + Ic({required this.name, this.tile = 'MaterialIcons'}); +} diff --git a/lib/screens/splash/intro_screen.dart b/lib/screens/splash/intro_screen.dart new file mode 100644 index 0000000..2d02d95 --- /dev/null +++ b/lib/screens/splash/intro_screen.dart @@ -0,0 +1,217 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:carousel_slider/carousel_slider.dart'; +import 'package:provider/provider.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class introScreen extends StatefulWidget { + const introScreen({Key? key}) : super(key: key); + + @override + State<introScreen> createState() => _introScreenState(); +} + +class _introScreenState extends State<introScreen> { + int? _currentSlide; + CarouselController? buttonCarouselController; + + @override + void initState() { + super.initState(); + _currentSlide = 0; // Inisialisasi _currentSlide di initState + buttonCarouselController = CarouselController(); + } + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return GestureDetector( + onHorizontalDragUpdate: (details) { + if (details.primaryDelta! > 0) { + buttonCarouselController?.previousPage( + duration: Duration(milliseconds: 300), curve: Curves.linear); + } else if (details.primaryDelta! < 0) { + buttonCarouselController?.nextPage( + duration: Duration(milliseconds: 300), curve: Curves.linear); + } + }, + child: Scaffold( + body: Stack( + children: [ + Positioned( + top: getProportionateScreenHeight(37), + left: 0, + right: 0, + child: _currentSlide == 2 + ? Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(15)), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () async { + await UsersInfo().setStateintro('true'); + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, + (Route<dynamic> route) => false); + }, + child: Text( + 'Login', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenHeight(11)), + ), + ) + ], + ), + ) + : SizedBox(), // Jika _currentSlide bukan 2, widget tidak ditampilkan + ), + Positioned( + top: getProportionateScreenHeight(40), + left: 0, + right: 0, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(3, (index) { + return AnimatedContainer( + duration: Duration(milliseconds: 300), + width: _currentSlide == index ? 20.0 : 8.0, + height: 8.0, + margin: EdgeInsets.symmetric(horizontal: 3.0), + decoration: BoxDecoration( + borderRadius: _currentSlide == index + ? BorderRadius.circular(50) + : BorderRadius.circular(50), + shape: _currentSlide == index + ? BoxShape.rectangle + : BoxShape.rectangle, + color: _currentSlide == index ? fourthColor : Colors.grey, + ), + ); + }), + ), + ), + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + 'assets/images/intro_image.png', + width: getProportionateScreenWidth(200), + ), + Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(15), + bottom: getProportionateScreenHeight(15)), + child: CarouselSlider( + carouselController: buttonCarouselController, + options: CarouselOptions( + height: getProportionateScreenHeight(150), + enableInfiniteScroll: false, + viewportFraction: 1.0, + initialPage: 0, // Change this to 0 + onPageChanged: (index, reason) { + setState(() { + _currentSlide = index; + }); + }, + ), + items: [ + "WELCOME TO VOCASIA -\nUNLOCK YOUR LEARNING\nPOTENTIAL!", + "EXPLORE BOUNDLESS\nKNOWLEDGE - ANYTIME,\nANYWHERE.", + "JOIN OUR COMMUNITY OF\nLEARNERS - LET'S GROW\nTOGETHER!" + ].map((i) { + return Builder( + builder: (BuildContext context) { + return Text( + '$i', + style: thirdTextStyle.copyWith( + fontWeight: FontWeight.w900, + fontSize: getProportionateScreenWidth(23), + color: + Theme.of(context).colorScheme.onBackground, + ), + textAlign: TextAlign.center, + ); + }, + ); + }).toList(), + ), + ), + ], + ), + ), + Positioned( + bottom: getProportionateScreenHeight(25), + left: 0, + right: 0, + child: Center( + child: GestureDetector( + onTap: () async { + await UsersInfo().setStateintro('true'); + if (_currentSlide == 2) { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, (Route<dynamic> route) => false); + print('nanti'); + } else { + buttonCarouselController?.nextPage( + duration: Duration(milliseconds: 300), + curve: Curves.linear); + } + }, + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + width: _currentSlide == 2 + ? getProportionateScreenWidth(200) + : 55.0, + height: _currentSlide == 2 + ? getProportionateScreenWidth(60) + : 55.0, + margin: EdgeInsets.symmetric(horizontal: 3.0), + decoration: BoxDecoration( + borderRadius: _currentSlide == 2 + ? BorderRadius.circular(15) + : BorderRadius.circular(50), + shape: _currentSlide == 2 + ? BoxShape.rectangle + : BoxShape.rectangle, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + child: Center( + child: _currentSlide == 2 + ? AnimatedOpacity( + duration: Duration(milliseconds: 1000), + opacity: _currentSlide == 2 ? 1.0 : 0.0, + child: Text( + "Let's Explore", + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 20, + ), + ), + ) + : Icon( + Icons.arrow_forward_rounded, + color: Colors.white, + ), + ), + ), + )), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/splash/splash_screen_login.dart b/lib/screens/splash/splash_screen_login.dart new file mode 100644 index 0000000..adf21a2 --- /dev/null +++ b/lib/screens/splash/splash_screen_login.dart @@ -0,0 +1,194 @@ +import 'dart:async'; + +// import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/providers/incomplete_profile_provider.dart'; +// import 'package:initial_folder/providers/notification_provider.dart'; +import 'package:initial_folder/providers/user_info_provider.dart'; +// import 'package:initial_folder/screens/home/components/notification.dart'; +import 'package:initial_folder/screens/home/home_screen.dart'; +import 'package:initial_folder/screens/login/login_screen.dart'; +import 'package:initial_folder/screens/login/login_with_email/login_email_screen.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/incomplete_profile_screen.dart'; +import 'package:initial_folder/providers/firebase_authentication_provider.dart'; +import 'package:initial_folder/providers/page_provider.dart'; +import 'package:initial_folder/screens/splash/intro_screen.dart'; +import 'package:initial_folder/services/auth_service.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../../size_config.dart'; + +class SplashScreenLogin extends StatefulWidget { + const SplashScreenLogin({Key? key}) : super(key: key); + + static String routeName = "/splash"; + + @override + State<SplashScreenLogin> createState() => _SplashScreenLoginState(); +} + +class Condition { + static bool loginFirebase = false; + static bool loginEmail = false; +} + +class _SplashScreenLoginState extends State<SplashScreenLogin> + with TickerProviderStateMixin { + late final AnimationController _controller = AnimationController( + duration: const Duration(seconds: 1), + vsync: this, + )..forward(); + late final Animation<double> _animation = CurvedAnimation( + parent: _controller, + curve: Curves.easeIn, + ); + + @override + void initState() { + super.initState(); + // getIntroScreen(); + Timer(const Duration(seconds: 3), () { + if (mounted) { + isLogin(); + } + }); + } + + void isLogin() async { + var token = await UsersInfo().getToken(); + var email = await UsersInfo().getEmail(); + var stateintro = await UsersInfo().getStateintro(); + + var isTokenExpired = await AuthService().checkToken(token); + print("Token expired? ${isTokenExpired}"); + if (isTokenExpired) { + print('masuk sini'); + UsersInfo().logout(); + Provider.of<FirebaseAuthenticationProvider>(context, listen: false) + .logout(); + Provider.of<PageProvider>(context, listen: false).remove(); + Navigator.of(context).pushNamedAndRemoveUntil( + LoginScreen.routeName, (Route<dynamic> route) => false); + + print("state intro ${stateintro}"); + if (stateintro == 'true') { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => LoginEmail(), + ), + ); + } else { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => introScreen(), + ), + ); + } + } else { + await Provider.of<UserInfoProvider>(context, listen: false) + .getUserInfo(email); + + Condition.loginFirebase = true; + Condition.loginEmail = true; + + if (!mounted) return; + IncompleteProfileProvider userInfoIncomplete = + Provider.of<IncompleteProfileProvider>(context, listen: false); + await userInfoIncomplete.getUserInfoIncomplete(); + print("User info incomplete ${userInfoIncomplete.isUserInfoComplete}"); + if (userInfoIncomplete.isUserInfoComplete == null) { + Navigator.of(context).pushNamedAndRemoveUntil( + LoginEmail.routeName, (Route<dynamic> route) => false); + return; + } + + if (userInfoIncomplete.isUserInfoComplete!) { + Navigator.of(context).pushNamedAndRemoveUntil( + HomeScreen.routeName, (Route<dynamic> route) => false); + } else { + Navigator.of(context).pushNamedAndRemoveUntil( + IncompleteProfile.routeName, (Route<dynamic> route) => false); + } + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + String imagePath = Theme.of(context).brightness == Brightness.dark + ? "assets/images/VOCASIA logo.png" + : "assets/images/Logo2.png"; + + SizeConfig().init(context); + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Flexible( + flex: 10, + child: FadeTransition( + opacity: _animation, + child: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset( + imagePath, + width: getProportionateScreenWidth(140), + ), + Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Make You Competent', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenHeight(11), + color: fourthColor), + ), + ) + // SizedBox( + // height: getProportionateScreenHeight(10), + // ), + // Text( + // "Make You Competent", + // style: primaryTextStyle.copyWith( + // fontWeight: reguler, + // fontSize: getProportionateScreenWidth(12.0), + // color: secondaryColor), + // ), + ], + ), + ), + ), + ), + Flexible( + flex: 1, + child: Padding( + padding: EdgeInsets.only( + bottom: getProportionateScreenHeight( + 25)), // Ubah sesuai kebutuhan Anda + child: Align( + alignment: Alignment.bottomCenter, + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 3, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/screens/whislist/my_whislist_page.dart b/lib/screens/whislist/my_whislist_page.dart new file mode 100644 index 0000000..ba20d73 --- /dev/null +++ b/lib/screens/whislist/my_whislist_page.dart @@ -0,0 +1,196 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/whislist_provider.dart'; +import 'package:initial_folder/screens/home/components/body_comp/latest_course.dart'; +import 'package:initial_folder/screens/home/components/body_comp/populer_course.dart'; +import 'package:initial_folder/screens/splash/splash_screen_login.dart'; +import 'package:initial_folder/widgets/wishlist_page.dart'; +import 'package:provider/provider.dart'; +import '../../size_config.dart'; +import '../../theme.dart'; + +class WishlistPage extends StatelessWidget { + const WishlistPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + Widget noWishlist() { + return Center( + child: Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(40), + ), + child: Text( + 'Kamu belum login, silahkan login untuk melihat wishlist', + textAlign: TextAlign.center, + style: primaryTextStyle, + ), + ), + ); + } + + Widget wishlist() { + return Consumer<WishlistProvider>( + builder: (BuildContext context, state, _) { + if (state.state == ResultState.Loading) { + return Center( + child: CircularProgressIndicator( + color: secondaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.NoData) { + print("State is NoData - showing empty wishlist message"); + return SingleChildScrollView( + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SizedBox(height: 35), + Container( + width: getProportionateScreenWidth(120), + height: getProportionateScreenWidth(120), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(2), + ), + child: Image.asset( + 'assets/images/kursuskosong.png', + scale: 1, + color: Theme.of(context).colorScheme.onBackground, + ), + ), + SizedBox(height: 16), + Text( + "Tidak ada wishlist", + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 1, + fontSize: getProportionateScreenWidth(14), + ), + ), + SizedBox(height: 4), + Container( + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16)), + child: Text( + "Kamu belum memiliki wishlist, cari kursus sekarang untuk menyimpan kursus yang ingin dibeli nanti", + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(25)), + PopulerCourse( + text: 'Kursus Teratas', + ), + LatestCourse( + text: 'Kursus Terbaru', + ), + ], + ), + ), + ); + } else if (state.state == ResultState.HasData) { + print("State punya data"); + return RefreshIndicator( + displacement: 40, + color: primaryColor, + onRefresh: () async { + await Provider.of<WishlistProvider>(context, listen: false) + .getWishlist(); + }, + child: SingleChildScrollView( + physics: + AlwaysScrollableScrollPhysics(), // Memastikan SingleChildScrollView bisa selalu scroll + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.vertical( + bottom: Radius.circular(15)), + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == + Brightness.dark + ? Colors.transparent + : Colors.grey, + spreadRadius: 0.06, + blurRadius: 5, + offset: Offset(0, 2), // Shadow position + ), + ]), + child: Padding( + padding: EdgeInsets.only( + top: getProportionateScreenHeight(20), + bottom: getProportionateScreenHeight(20)), + child: ListView.builder( + shrinkWrap: true, + physics: + NeverScrollableScrollPhysics(), // ListView tidak perlu scroll sendiri karena sudah di dalam SingleChildScrollView + itemCount: state.result!.data[0].length, + itemBuilder: (context, index) { + var wishlistCourse = state.result!.data[0][index]; + return MyWishlistPage( + wishlistDataModel: wishlistCourse, + ); + }, + ), + )), + SizedBox( + height: getProportionateScreenHeight(25), + ), + PopulerCourse( + text: 'Kursus Teratas', + ), + LatestCourse( + text: 'Kursus Terbaru', + ), + ], + ), + ), + ); + } else if (state.state == ResultState.Error) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Coba Lagi'), + IconButton( + onPressed: () async { + await Provider.of<WishlistProvider>(context, + listen: false) + .getWishlist(); + }, + icon: Icon(Icons.refresh)) + ], + ); + } else { + return Center(child: Text('')); + } + }, + ); + } + + return Scaffold( + appBar: AppBar( + elevation: 0.0, + backgroundColor: Theme.of(context).colorScheme.background, + title: Text('Wishlist', + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + letterSpacing: 2.3, + fontSize: getProportionateScreenWidth(20))), + ), + body: Container( + child: (Condition.loginEmail || Condition.loginFirebase) + ? wishlist() + : noWishlist()), + ); + } +} diff --git a/lib/screens/whislist/wishlist_card.dart b/lib/screens/whislist/wishlist_card.dart new file mode 100644 index 0000000..b33e09e --- /dev/null +++ b/lib/screens/whislist/wishlist_card.dart @@ -0,0 +1,364 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/whislist_provider.dart'; +import 'package:initial_folder/providers/wishlist_post_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../../providers/theme_provider.dart'; +import '../../size_config.dart'; +import '../../theme.dart'; + +class WishlistCard extends StatelessWidget { + const WishlistCard({ + Key? key, + this.width = 120, + this.pad = 20, + required this.thumbnail, + required this.id, + required this.isTopCourse, + required this.title, + required this.instructorName, + required this.rating, + required this.specificRating, + required this.numberOfRatings, + required this.price, + required this.realPrice, + required this.press, + required this.courseId, + }) : super(key: key); + + final double width; + final double pad; + final String thumbnail, + title, + instructorName, + price, + realPrice, + rating, + specificRating, + numberOfRatings, + id, + isTopCourse, + courseId; + final VoidCallback press; + // final VoidCallback? whislistPress; + // final Widget? iconWishlist; + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + WishlistPostProvider wishlistPostProvider = + Provider.of<WishlistPostProvider>(context); + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + //CourseProvider courseProvider = Provider.of<CourseProvider>(context); + // final formatCurrency = + // new NumberFormat.simpleCurrency(decimalDigits: 0, locale: 'id_ID'); + return Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(15), + right: getProportionateScreenWidth(15), + bottom: getProportionateScreenHeight(10)), + child: Container( + width: getProportionateScreenWidth(320), + // padding: EdgeInsets.only(left: getProportionateScreenWidth(pad)), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: isDarkMode ? Colors.black : Colors.grey, + spreadRadius: 0.01, + blurRadius: 2, + offset: Offset(0, 1), // Shadow position + ), + ], + ), + child: Padding( + padding: EdgeInsets.all(3), + child: InkWell( + onTap: press, + child: Container( + width: getProportionateScreenWidth(width), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(170), + height: getProportionateScreenHeight(85), + padding: EdgeInsets.all(10), + child: AspectRatio( + aspectRatio: 1.7, + child: CachedNetworkImage( + imageUrl: thumbnail, + imageBuilder: (context, imageProvider) => + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.vertical( + top: Radius.circular(5), + bottom: Radius.circular(5)), + image: DecorationImage( + image: imageProvider, + fit: BoxFit.fill, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + color: thirdColor, + ), + gradient: LinearGradient(stops: [ + 0.4, + 0.5, + 0.6 + ], colors: [ + secondaryColor, + thirdColor, + secondaryColor + ]), + ), + errorWidget: (context, url, error) => + Icon(Icons.error), + ), + ), + ), + ]), + Flexible( + flex: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: getProportionateScreenWidth(10)), + Container( + padding: EdgeInsets.only(left: 2, right: 5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Flexible( + child: Text( + title, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: + getProportionateScreenWidth(12), + color: Theme.of(context) + .colorScheme + .onPrimary, + fontWeight: semiBold), + maxLines: 2, + overflow: TextOverflow.ellipsis, + )), + SizedBox( + width: getProportionateScreenWidth(15), + ), + Padding( + padding: EdgeInsets.all(5), + child: GestureDetector( + onTap: () async { + await wishlistPostProvider + .deleteWishlist( + int.parse(courseId)); + await Provider.of<WishlistProvider>( + context, + listen: false) + .getWishlist(); + print('X ditekan' + id); + }, + child: Icon( + FontAwesomeIcons.x, + color: Theme.of(context) + .colorScheme + .onPrimary, + size: + getProportionateScreenWidth(15), + ), + ), + ) + ], + ), + SizedBox( + height: getProportionateScreenWidth(6)), + RichText( + text: new TextSpan( + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth(10), + color: secondaryColor), + children: <TextSpan>[ + new TextSpan( + text: 'Oleh ', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + ), + ), + new TextSpan( + text: instructorName, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + ), + ), + ], + ), + ), + SizedBox( + height: getProportionateScreenWidth(6)), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + price, + style: thirdTextStyle.copyWith( + letterSpacing: 0.5, + fontSize: + SizeConfig.blockHorizontal! * 3, + fontWeight: reguler, + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + (isTopCourse == 1.toString()) + ? Container( + alignment: Alignment.center, + width: + getProportionateScreenWidth( + 48), + child: Text( + 'Populer', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: Theme.of(context) + .colorScheme + .onPrimary, + fontSize: SizeConfig + .blockHorizontal! * + 2.5, + fontWeight: reguler), + ), + decoration: BoxDecoration( + color: Color(0xffECEB98), + borderRadius: + BorderRadius.circular(5), + ), + ) + : SizedBox(height: 0, width: 0), + // InkWell( + // borderRadius: BorderRadius.circular(50), + // onTap: () {}, + // child: Container( + // padding: EdgeInsets.all(getProportionateScreenWidth(8)), + // height: getProportionateScreenWidth(28), + // width: getProportionateScreenWidth(28), + + // child: Icon(Icons.favorite,color: sevenColor, size: 10,) + // ), + // ), + ], + ), + SizedBox( + height: getProportionateScreenWidth(6)), + (realPrice != 0.toString()) + ? Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + realPrice, + style: + thirdTextStyle.copyWith( + decoration: TextDecoration + .lineThrough, + fontSize: + getProportionateScreenWidth( + 10), + fontWeight: reguler, + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + // IconButton( + // onPressed: whislistPress, + // icon: iconWishlist) + ], + ), + SizedBox( + height: + getProportionateScreenWidth( + 6)), + ], + ) + : SizedBox(), + Center( + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + DetailCourseScreen( + idcourse: courseId, + ), + ), + ); + }, + child: Container( + height: 35, + // width: 120, + decoration: BoxDecoration( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + borderRadius: + BorderRadius.circular(5)), + + child: Center( + child: price == 'Gratis' + ? Text( + 'Dapatkan Kursus', + style: thirdTextStyle.copyWith( + fontSize: 12, + color: Colors.white), + ) + : Text( + 'Beli Kursus', + style: thirdTextStyle.copyWith( + fontSize: 12, + color: Colors.white), + ), + ), + ), + )), + SizedBox( + height: getProportionateScreenWidth(6)), + ], + ), + ) + ], + ), + ) + ], + ), + ), + ), + ))); + } +} diff --git a/lib/services/all_certificate_service.dart b/lib/services/all_certificate_service.dart new file mode 100644 index 0000000..0643845 --- /dev/null +++ b/lib/services/all_certificate_service.dart @@ -0,0 +1,183 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/certificate_model.dart'; +import 'package:initial_folder/models/check_certificate.model.dart'; +import 'package:initial_folder/models/my_certificate.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:pdf/widgets.dart' as pw; +import 'package:pdf/pdf.dart'; + +class AllCertificateServices { + Future<Map<String, dynamic>> uploadCertificate( + String idPayment, File pdfFile) async { + String? token = await UsersInfo().getToken(); + + try { + Uri url = Uri.parse("$baseUrl/users/upload-sertifikat/$idPayment"); + + var request = http.MultipartRequest('POST', url); + + request.headers.addAll(headerWithToken(token)); + request.files + .add(await http.MultipartFile.fromPath('file', pdfFile.path)); + + var response = await request.send(); + var responseData = await response.stream.bytesToString(); + var jsonData = json.decode(responseData); + + if (response.statusCode == 200) { + throw ("Sertifikat berhasil di unduh"); + } else { + throw Exception('Gagal mengunggah sertifikat: ${jsonData['message']}'); + } + } catch (e) { + return {'status': 500, 'error': true, 'message': e.toString()}; + } + } + + Future getAllCertificate() async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + + Uri url = Uri.parse("$baseUrl/homepage/get_all_sertifikat/$idUser"); + + var response = await http.get(url, headers: headerWithToken(token!)); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<DataMyCertificateModel> certif = []; + + for (var item in data) { + certif.add(DataMyCertificateModel.fromJson(item)); + } + + return certif; + } else { + throw Exception('Data Kursus Teratas Gagal Diambil'); + } + } + + Future getSertif(String idCourse) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + + Uri url = + Uri.parse("$baseUrl/homepage/sertifikat/$idUser?course_id=$idCourse"); + var response = await http.get(url, headers: headerWithToken(token!)); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data']; + List<CertificateModel> certif = []; + + for (var item in data) { + certif.add(CertificateModel.fromJson(item)); + } + + print("ini data sertif dari api : ${response.body}"); + + return certif; + } else { + throw Exception('Data Kursus Teratas Gagal Diambil'); + } + } + + Future checkSertif(String text) async { + print("Ini sertif ${text}"); + Uri url = Uri.parse("$baseUrl/homepage/cek_status_sertifikat/$text"); + var response = await http.get(url); + var data = jsonDecode(response.body); + + if (data['status'] == 200) { + if (data != null && data['data'] != null && data['data'].length > 0) { + var certifData = data['data'][0]; + CheckCertificateData certif = CheckCertificateData.fromJson(certifData); + print("Berhasil cek sertif ${response.body}"); + return certif; + } else { + print("Kosong cek sertif ${response.body}"); + return null; + } + } else if (data['status'] == 404) { + print("Gagal cek sertif ${response.body}"); + return data['massages'] != null + ? data['massages'] + : "Error message not available"; + } + } + + Future searchCertif(String keyword) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + + Uri url = Uri.parse( + "$baseUrl/homepage/search_sertifikat/$idUser?keyword=$keyword"); + + var response = await http.get(url, headers: headerWithToken(token!)); + + List<DataMyCertificateModel> certif = []; + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + + for (var item in data) { + certif.add(DataMyCertificateModel.fromJson(item)); + } + + return certif; + } else if (response.statusCode == 400) { + return certif; + } else { + return certif; + } + } + +Future<void> convertAndUpload(GlobalKey _globalKey, String idPayment) async { + try { + RenderRepaintBoundary boundaryRenderObject = + _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary; + ui.Image image = await boundaryRenderObject.toImage(pixelRatio: 3.0); + ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + + final pdf = pw.Document(); + + final pdfPageFormat = PdfPageFormat.a4.landscape.copyWith( + marginBottom: 0, + marginTop: 0, + marginLeft: 0, + marginRight: 0, + ); + + pdf.addPage( + pw.Page( + pageFormat: pdfPageFormat, + build: (pw.Context context) { + return pw.Center( + child: pw.Image(pw.MemoryImage(pngBytes), + fit: pw.BoxFit.cover, ), + ); + }, + ), + ); + + Directory tempDir = await getTemporaryDirectory(); + String tempPath = '${tempDir.path}/certificate_${idPayment}.pdf'; + File tempFile = File(tempPath); + + await tempFile.writeAsBytes(await pdf.save()); + + await uploadCertificate(idPayment, tempFile); + } catch (e) { + throw Exception('Gagal mengonversi PNG ke PDF: $e'); + } +} + +} diff --git a/lib/services/announcement_service.dart b/lib/services/announcement_service.dart new file mode 100644 index 0000000..6f499df --- /dev/null +++ b/lib/services/announcement_service.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/models/section_model.dart'; + +class AnnouncementService { + Future<AnnouncementModel> getAnnouncement(String idCourse) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/announcement/$idCourse'); + + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + + if (response.statusCode == 200) { + return AnnouncementModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal ambil data'); + } + } + + Future<bool> replyAnnouncement( + String tokenAnnouncement, String textBody, String idAnnouncement) async { + int? userId = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/announcement/reply'); + + var body = jsonEncode({ + "sender": userId, + "token": tokenAnnouncement, + "replies": idAnnouncement, + "body": textBody, + }); + + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + + if (response.statusCode == 201) { + return true; + } else { + return false; + } + } + + Future<bool> likeAnnouncement(String tokenAnnouncement) async { + int? userId = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/announcement/like'); + + var body = jsonEncode({"id_user": userId, "token": tokenAnnouncement}); + + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } +} diff --git a/lib/services/auth_service.dart b/lib/services/auth_service.dart new file mode 100644 index 0000000..c81fdec --- /dev/null +++ b/lib/services/auth_service.dart @@ -0,0 +1,115 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/user_model.dart'; +import 'package:http/http.dart' as http; + +class AuthService { + Future<UserModel> register({ + required String name, + required String email, + required String password, + required String phoneNumber, + }) async { + print('[DEBUG] Register called with: name=$name, email=$email, phone=$phoneNumber'); + Uri url = Uri.parse('$baseUrl/auth/register'); + var body = jsonEncode({ + 'name': name, + 'email': email, + 'password': password, + 'phone': phoneNumber, + }); + + print('[DEBUG] Register URL: $url'); + print('[DEBUG] Register Body: $body'); + + var response = await http.post(url, headers: baseHeader, body: body); + print('[DEBUG] Register Response: ${response.statusCode} ${response.body}'); + if (response.statusCode == 201) { + return UserModel.fromJson(jsonDecode(response.body)['data']); + } else if (response.statusCode == 403) { + var responseData = jsonDecode(response.body)['data']['message']; + print('[DEBUG] Register 403 ResponseData: $responseData'); + if (responseData.containsKey('email')) { + throw ('Failed\n\nEmail sudah terdaftar'); + } else if (responseData.containsKey('phone')) { + throw ('Failed\n\nNomor HP sudah terdaftar'); + } else { + throw ('Registrasi gagal. Mohon coba lagi.'); + } + } else { + throw ('Registrasi gagal. Mohon coba lagi.'); + } + } + + Future<UserModel> login( + {required String email, required String password}) async { + print('[DEBUG] Login called with: email=$email'); + Uri url = Uri.parse('$baseUrl/auth/mobile/login'); + var body = jsonEncode({ + 'email': email, + 'password': password, + }); + print('[DEBUG] Login URL: $url'); + print('[DEBUG] Login Body: $body'); + var response = await http.post(url, headers: baseHeader, body: body); + print('[DEBUG] Login Response: ${response.statusCode} ${response.body}'); + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data']; + UserModel user = UserModel.fromJson(data); + user.token = data['token']; + + await UsersInfo().setToken(user.token!); + + return user; + } else { + throw Exception('Gagal login mobile'); + } + } + + Future<bool> googleSignInAuth({ + required String idToken, + }) async { + print('[DEBUG] GoogleSignInAuth called with idToken=$idToken'); + Uri url = Uri.parse('$baseUrl/auth/googlecheck/signin'); + var body = jsonEncode({ + 'id_token': idToken, + }); + print('[DEBUG] GoogleSignInAuth URL: $url'); + print('[DEBUG] GoogleSignInAuth Body: $body'); + var response = await http.post(url, headers: baseHeader, body: body); + print('[DEBUG] GoogleSignInAuth Response: ${response.statusCode} ${response.body}'); + + if (response.statusCode == 200 || response.statusCode == 403) { + var res = jsonDecode(response.body)['data']; + print('[DEBUG] GoogleSignInAuth Token: ${res['token']}'); + await UsersInfo().setToken(res['token']); + return true; + } else if (response.statusCode == 400) { + print("Gagal login 400 ${response.body}"); + throw Exception('Akun anda belum terdaftar'); + } else { + print("Gagal login google ${response.body}"); + throw Exception('Gagal login'); + } + } + + Future<bool> checkToken(String? token) async { + print('[DEBUG] checkToken called with token=$token'); + Uri url = Uri.parse('$baseUrl/users/course/my'); + print('[DEBUG] checkToken URL: $url'); + var response = await http.get(url, headers: headerWithToken(token)); + print('[DEBUG] checkToken Response: ${response.statusCode} ${response.body}'); + if (response.statusCode == 200) { + print("Berhasil token ${response.body}"); + return false; + } else if (response.statusCode == 404) { + print("404 token ${response.body}"); + return false; + } else { + print("Gagal token ${response.body}"); + return true; + } + } +} diff --git a/lib/services/banners_service.dart b/lib/services/banners_service.dart new file mode 100644 index 0000000..16c032d --- /dev/null +++ b/lib/services/banners_service.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/banners_model.dart'; +import 'package:http/http.dart' as http; + +class BannersService { + Future<Map<String, dynamic>> getAllBanners() async { + Uri url = Uri.parse('$baseUrl/mobile/banners'); + var header = { + 'Content-Type': 'application/json; charset=UTF-8', + 'Connection': 'Keep-Alive' + }; + var response = await http.get(url, headers: header); + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + + List<BannersModel> banners = []; + print("Berhasil banner${response.body}"); + for (var item in data) { + if (item['status'] == '1') { + banners.add(BannersModel.fromJson(item)); + } + } + return {'status': response.statusCode, 'data': banners}; + } else if (response.statusCode == 404) { + print("Banner kosong, saat ini running banner default. ${response.body}"); + return {'status': response.statusCode, 'data': []}; + } else { + print("Gagal banner ${response.body}"); + throw Exception('Gagal ambil data'); + } + } +} diff --git a/lib/services/cancel_payment_service.dart b/lib/services/cancel_payment_service.dart new file mode 100644 index 0000000..51aadd2 --- /dev/null +++ b/lib/services/cancel_payment_service.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:http/http.dart' as http; + +class CancelPaymentService { + Future<String> cancelPayment(String paymentId) async { + print("Ini id payment?${paymentId}"); + var token = await UsersInfo().getToken(); + var idUser = await UsersInfo().getIdUser(); + + Uri url = Uri.parse('$baseUrl/payment/cancel-payment'); + + var body = jsonEncode({ + 'id_payment': paymentId, + }); + + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + if (response.statusCode == 201) { + print("Berhasil cancel cuy${response.body}"); + return 'Transaksi berhasil dibatalkan'; + } else { + print("Ga berhasil cancel cuy${response.body}"); + return 'Gagal, pembayaran sudah dibatalkan atau kadaluwarsa'; + } + } +} diff --git a/lib/services/cart_service.dart b/lib/services/cart_service.dart new file mode 100644 index 0000000..544a8d9 --- /dev/null +++ b/lib/services/cart_service.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:http/http.dart' as http; +import 'package:initial_folder/models/carts_model.dart'; + +class CartService { + Future<bool> addCart(idCourse) async { + var token = await UsersInfo().getToken(); + var idUser = await UsersInfo().getIdUser(); + + Uri url = Uri.parse('$baseUrl/users/add-to-cart'); + + var body = jsonEncode({ + // 'id_user': idUser, + 'cart_item': idCourse, + }); + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + if (response.statusCode == 201) { + return true; + } else { + throw Exception('Gagal Menambahkan ke keranjang'); + } + } + + Future<bool> deleteCart(idCart) async { + var token = await UsersInfo().getToken(); + + Uri url = Uri.parse('$baseUrl/users/cart/delete/$idCart'); + + http.Response response = await http.delete( + url, + headers: headerWithToken(token!), + ); + if (response.statusCode == 200) { + return true; + } else { + throw Exception('Gagal Menghapus keranjang'); + } + } + + Future<CartsModel> getCarts() async { + var token = await UsersInfo().getToken(); + // var idUser = await UsersInfo().getIdUser(); + + // Uri url = Uri.parse('$baseUrl/users/carts/$idUser'); + Uri url = Uri.parse('$baseUrl/users/carts'); + http.Response response = await http.get( + url, + headers: headerWithToken(token!), + ); + log(response.body); + if (response.statusCode == 200) { + print("Ini isi cart berhasil ${response.body}"); + return CartsModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal Mengambil data keranjang'); + } + } +} diff --git a/lib/services/categories_service.dart b/lib/services/categories_service.dart new file mode 100644 index 0000000..66e2c10 --- /dev/null +++ b/lib/services/categories_service.dart @@ -0,0 +1,48 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/catagories_model.dart'; +import 'package:http/http.dart' as http; +import 'package:initial_folder/models/subcategories_model.dart'; + +class CategoriesService { + Future<List<CategoriesModel>> getAllCategories() async { + Uri url = Uri.parse('$baseUrl/homepage/categories'); + + var header = {'Content-Type': 'application/json; charset=UTF-8'}; + var response = await http.get(url, headers: header); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CategoriesModel> categories = []; + int categoriesAdded = 0; + + for (var item in data) { + if (item['parent_category'] == '0' && categoriesAdded < 9) { + categories.add(CategoriesModel.fromJson(item)); + categoriesAdded++; + } + } + + return categories; + } else { + throw Exception('Gagal ambil data'); + } + } + + Future<List<SubCategoryModel>> getSubCategories(String id) async { + Uri url = Uri.parse('$baseUrl/homepage/subcategories/$id'); + var header = {'Content-Type': 'application/json; charset=UTF-8'}; + var response = await http.get(url, headers: header); + + if (response.statusCode == 200) { + List<dynamic> data = jsonDecode(response.body); + List<SubCategoryModel> subCategories = data + .map((subCategoryJson) => SubCategoryModel.fromJson(subCategoryJson)) + .toList(); + return subCategories; + } else { + throw Exception('Failed to load subcategories'); + } + } +} diff --git a/lib/services/coupon_service.dart b/lib/services/coupon_service.dart new file mode 100644 index 0000000..8fe7532 --- /dev/null +++ b/lib/services/coupon_service.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/discount_course_model.dart'; +import 'package:http/http.dart' as http; + +class CouponService { + Future<dynamic> getDiscountCourse(coupon) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/exchange-coupon?voucher=$coupon'); + print("Ini url api ${url}"); + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + if (response.statusCode == 200) { + print("Berhasil kupon ${response.body}"); + Map body = jsonDecode(response.body); + var data = body['data']; + List<DiscountCourseModel> course = []; + String message = ''; + + if (data is List) { + for (var item in data) { + course.add(DiscountCourseModel.fromJson(item)); + } + return course; + } else { + message = data['message']; + return message; + } + } else { + print("Gagal kupon ${response.body}"); + throw Exception('Data Kursus kupon gagal diambil'); + } + } +} diff --git a/lib/services/course_by_category_service.dart b/lib/services/course_by_category_service.dart new file mode 100644 index 0000000..d06d7a6 --- /dev/null +++ b/lib/services/course_by_category_service.dart @@ -0,0 +1,51 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/course_model.dart'; + +class CourseByCategoryService { + Future<List<CourseModel>> getCourseCategoryAndSub(categoryId, subId) async { + print('ID kategori: $categoryId'); + print('ID SUb kategori: $subId'); + Uri url = Uri.parse( + '$baseUrl/homepage/courses?category=$categoryId&subcategory=$subId'); + var header = {'Content-Type': 'application/json; charset=UTF-8'}; + var response = await http.get(url, headers: header); + print(url); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> courseByCategory = []; + for (var item in data) { + courseByCategory.add(CourseModel.fromJson(item)); + } + print("Berhasil kursus sub kategori ${response.body}"); + return courseByCategory; + } else { + print("Gagal kursus sub kategori ${response.body}"); + throw Exception('Gagal ambil data'); + } + } + + Future<List<CourseModel>> getCourseOnlyCategory(categoryId) async { + print('ID kategori: $categoryId'); + Uri url = Uri.parse('$baseUrl/homepage/courses?category=$categoryId'); + var header = {'Content-Type': 'application/json; charset=UTF-8'}; + var response = await http.get(url, headers: header); + print(url); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> courseByCategory = []; + for (var item in data) { + courseByCategory.add(CourseModel.fromJson(item)); + } + print("Berhasil kursus kategori ${response.body}"); + return courseByCategory; + } else { + print("Gagal kursus kategori ${response.body}"); + throw Exception('Gagal ambil data'); + } + } +} diff --git a/lib/services/course_service.dart b/lib/services/course_service.dart new file mode 100644 index 0000000..c0cc111 --- /dev/null +++ b/lib/services/course_service.dart @@ -0,0 +1,190 @@ +import 'dart:convert'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_course_model.dart'; +import 'package:initial_folder/models/detail_rating_course_model.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:initial_folder/models/my_course_model.dart'; +import 'package:http/http.dart' as http; + +class CourseService { + Future<List<CourseModel>> getOthersCourse(page) async { + Uri url = Uri.parse('$baseUrl/homepage/courses?page=$page&limit=10'); + + http.Response response = await http.get(url, headers: baseHeader); + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> course = []; + for (var item in data) { + course.add(CourseModel.fromJson(item)); + } + + return course; + } else { + throw Exception('Data Kursus lainnya Gagal Diambil'); + } + } + + Future<DetailCourseModel> getDetailCourse(String id) async { + Uri url = Uri.parse('$baseUrl/homepage/course/$id'); + String? token = await UsersInfo().getToken(); + var response; + if (token == null) { + response = await http.get(url, headers: baseHeader); + } else { + response = await http.get(url, headers: headerWithToken(token)); + } + if (response.statusCode == 200) { + print("Berhasil detail course ${response.body}"); + return DetailCourseModel.fromJson(jsonDecode(response.body)); + } else { + print("Gagal detail course ${response.body}"); + throw Exception('Data Detail Kursus Gagal Diambil'); + } + } + + Future<DataDetailCourseModel> getDetailCourseCoupon( + String id, String coupon) async { + Uri url = Uri.parse('$baseUrl/mobile/course-discount/$id/$coupon'); + http.Response response = await http.get(url, headers: baseHeader); + if (response.statusCode == 200) { + Map body = jsonDecode(response.body); + var data = body['data']; + print("Ini sukses detail kupon ${response.body}"); + return DataDetailCourseModel.fromJson(data); + } else { + print("Ini gagal detail kupon ${response.body}"); + throw Exception('Data Detail Kursus Gagal Diambil'); + } + } + + Future<DetailCourseModel> getDetailCourseLogin(String id) async { + String? token = await UsersInfo().getToken(); + + Uri url = Uri.parse('$baseUrl/homepage/course/$id'); + http.Response response = + await http.get(url, headers: headerWithToken(token)); + + if (response.statusCode == 200) { + print("Berhasil detail course ${response.body}"); + return DetailCourseModel.fromJson(jsonDecode(response.body)); + } else { + print("Gagal detail course ${response.body}"); + throw Exception('Data Detail Kursus Gagal Diambil'); + } + } + + Future<RatingCourseDetailModel> getRatingDetailCourse(String id) async { + Uri url = Uri.parse('$baseUrl/homepage/course/rating/$id'); + http.Response response = await http.get(url, headers: baseHeader); + if (response.statusCode == 200) { + return RatingCourseDetailModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Data Rating Detail Kursus Gagal Diambil'); + } + } + + Future<MyCourseModel> getMyCourse() async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/course/my'); + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + if (response.statusCode == 200) { + print('getmycourse berhasil'); + print("Berhasil getmycourse ${response.body}"); + return MyCourseModel.fromJson(jsonDecode(response.body)); + } else if (response.statusCode == 404) { + print('getmycourse eror'); + return MyCourseModel(data: [[]]); + } else { + print('getmycourse gagal'); + throw Exception('Data Kursus Saya, Gagal Diambil'); + } + } + + Future<MyCourseModel> getSearchMyCourse(String courseName) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse( + '$baseUrl/users/course/my_course_keyword_search?keyword=$courseName'); + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + if (response.statusCode == 200) { + print(response.body); + return MyCourseModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Data Kursus Saya, Gagal Diambil'); + } + } + + Future<List> postingReviewCourse( + String review, int courseId, int valueRating) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/review'); + var body = jsonEncode( + {"rating": valueRating, "course_id": courseId, "review": review}); + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + + if (response.statusCode == 201 || response.statusCode == 200) { + return [true, response.statusCode]; + } else { + return [false, 400]; + } + } + + Future<List<CourseModel>> getTopCourse() async { + Uri url = Uri.parse('$baseUrl/homepage/courses?top_course=1'); + + http.Response response = await http.get(url, headers: baseHeader); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> course = []; + for (var item in data) { + course.add(CourseModel.fromJson(item)); + } + + return course; + } else { + throw Exception('Data Kursus Teratas Gagal Diambil'); + } + } + + Future<List<CourseModel>> getPromoCourse() async { + Uri url = Uri.parse('$baseUrl/homepage/promo-courses'); + + http.Response response = await http.get(url, headers: baseHeader); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> course = []; + print("Berhasil data promo${response.body}"); + for (var item in data) { + course.add(CourseModel.fromJson(item)); + } + + return course; + }else if (response.statusCode == 404) { + print("Promo kosong, tidak ada promo. ${response.body}"); + return []; + } else { + throw Exception('Data Kursus Promo Gagal Diambil'); + } + } + + Future<List<CourseModel>> getLatestCourse() async { + Uri url = Uri.parse('$baseUrl/mobile/newcourse'); + + http.Response response = await http.get(url, headers: baseHeader); + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> course = []; + for (var item in data) { + course.add(CourseModel.fromJson(item)); + } + return course; + } else { + throw Exception('Data Kursus Teratas Gagal Diambil'); + } + } +} diff --git a/lib/services/current_lesson_service.dart b/lib/services/current_lesson_service.dart new file mode 100644 index 0000000..d961ddf --- /dev/null +++ b/lib/services/current_lesson_service.dart @@ -0,0 +1,27 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/lesson_model.dart'; +import 'package:http/http.dart' as http; + +class CurrentLessonService { + Future<LessonModel> getCurrentLesson(String lessonId) async { + String? token = await UsersInfo().getToken(); + + print( + "Ini Current Lesson ID ----------------------------------> : $lessonId"); + + Uri url = Uri.parse('$baseUrl/users/course/my/lesson/$lessonId'); + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + + print("Ini Current Lesson ------------------------> : ${response.body}"); + + if (response.statusCode == 200) { + return LessonModel.fromJson(jsonDecode(response.body)["data"][0]); + } else { + throw Exception('Data Lesson Gagal Diambil'); + } + } +} diff --git a/lib/services/detail_invoice_service.dart b/lib/services/detail_invoice_service.dart new file mode 100644 index 0000000..17e80c5 --- /dev/null +++ b/lib/services/detail_invoice_service.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/detail_invoice_model.dart'; +import 'package:http/http.dart' as http; + +class DetailInvoiceService { + Future<List<DataDetailInvoiceModel>> detailInvoice(String? orderId) async { + String? token = await UsersInfo().getToken(); + + Uri url = Uri.parse('$baseUrl/payment/invoice-detail/$orderId'); + + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + List<DataDetailInvoiceModel> detailOrder = []; + if (response.statusCode == 200) { + print("BERHASIL COKKKK"); + var data = jsonDecode(response.body); + print("Ini response berhasil get detail transaksi ${response.body}"); + + var list = data['data'] as List; + detailOrder = + list.map((item) => DataDetailInvoiceModel.fromJson(item)).toList(); + + return detailOrder; + } else { + print("GAGAL COKKKK"); + throw Exception('Data Transaksi Gagal Diambil'); + } + } +} diff --git a/lib/services/forgot_password_service.dart b/lib/services/forgot_password_service.dart new file mode 100644 index 0000000..e1601bc --- /dev/null +++ b/lib/services/forgot_password_service.dart @@ -0,0 +1,22 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import '../base_service.dart'; +import '../models/forgot_password_model.dart'; + +class ForgotService { + Future<ForgotPasswordModel> forgotPassword({ + required String email, + }) async { + Uri url = Uri.parse('$baseUrl/auth/mobile/forgot-password'); + var body = jsonEncode({ + 'email': email, + }); + + var response = await http.post(url, headers: baseHeader, body: body); + if (response.statusCode == 200) { + return ForgotPasswordModel.fromJson(jsonDecode(response.body)['data']); + } else { + throw response.body; + } + } +} diff --git a/lib/services/history_transactions_service.dart b/lib/services/history_transactions_service.dart new file mode 100644 index 0000000..de0b73e --- /dev/null +++ b/lib/services/history_transactions_service.dart @@ -0,0 +1,53 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:http/http.dart' as http; + +class HistoryTransactionService { + Future<List<HistoryTransactionModel>> historyTransactions() async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/payment/history'); + + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + + List<HistoryTransactionModel> history = []; + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + print("Data riwayat transaksi ${data}"); + int i = 0; + + for (var item in data) { + history.add(HistoryTransactionModel.fromJson(item)); + i++; + } + print("Berhasil riwayat transaksi ${response.body}"); + return history; + } else { + print("Gagal riwayat transaksi ${response.body}"); + throw Exception('Data Transaksi Gagal Diambil'); + } + } + + // Fungsi cek expired + Future<bool> checkTransactionExpiration(String orderId) async { + String? token = await UsersInfo().getToken(); + if (token == null) { + throw Exception('Token tidak ditemukan. Silakan login kembali.'); + } + + Uri url = Uri.parse('$baseUrl/payment/check-expiration/$orderId'); + http.Response response = + await http.post(url, headers: headerWithToken(token)); + + if (response.statusCode == 210) { + print('transaksi SUDAH KEDALUARSA'); + return false; + } else { + print("Error memeriksa masa berlaku transaksi: ${response.body}"); + throw Exception('Gagal memeriksa status transaksi'); + } + } +} diff --git a/lib/services/instructor_service.dart b/lib/services/instructor_service.dart new file mode 100644 index 0000000..45ec98f --- /dev/null +++ b/lib/services/instructor_service.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/instructor_model.dart'; +import 'package:http/http.dart' as http; + +class InstructorService { + Future<InstructorModel> getInstructorProfile(id) async { + Uri url = Uri.parse('$baseUrl/homepage/course/detail/instructor/$id'); + http.Response response = await http.get(url, headers: baseHeader); + print('instructor' + response.body); + if (response.statusCode == 200) { + return InstructorModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Data Profile Instruktur Gagal Diambil'); + } + } +} diff --git a/lib/services/lesson_course_service.dart b/lib/services/lesson_course_service.dart new file mode 100644 index 0000000..1b4e8cd --- /dev/null +++ b/lib/services/lesson_course_service.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/lesson_course_model.dart'; +import 'package:http/http.dart' as http; +import 'package:initial_folder/models/section_model.dart'; + +class LessonCourseService { + Future<LessonCourseModel> getLessonCourse(int id) async { + String? token = await UsersInfo().getToken(); + int? idUser = await UsersInfo().getIdUser(); + + // Uri url = + // Uri.parse('$baseUrl/users/course/my/lesson?course=$id&user=$idUser'); + Uri url = Uri.parse('$baseUrl/users/course/my/lesson?course=$id'); + + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + print(token); + print("disiiniii les ---->" + response.body); + if (response.statusCode == 200) { + return LessonCourseModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Data Lesson Kursus Saya, Gagal Diambil'); + } + } + + Future<SectionModel> getSectionCourse(int id) async { + String? token = await UsersInfo().getToken(); + int? idUser = await UsersInfo().getIdUser(); + + Uri url = Uri.parse( + '$baseUrl/users/course/my/lesson?course=$id&user=$idUser&is_mobile_request=1'); + + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + print("disiiniii sect ---->" + response.body); + if (response.statusCode == 200) { + return SectionModel.fromJson(jsonDecode(response.body)); + } else { + print('masuk sini juga'); + throw Exception('Data Lesson Kursus Saya, Gagal Diambil'); + } + } + + Future updateLessonCourse(lessonId, {progress = 1}) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + var body = jsonEncode({ + "id_user": idUser, + "lesson_id": lessonId, + "progress": progress, + }); + Uri url = Uri.parse('$baseUrl/users/lesson/update/progress'); + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + print(response.body); + if (response.statusCode == 200) { + return response.statusCode; + } else { + print(response.statusCode); + print('TERJADI ERROR DALAM UPDATE CEKLIS'); + // throw Exception('Gagal Update'); + } + } +} diff --git a/lib/services/local_notification_service.dart b/lib/services/local_notification_service.dart new file mode 100644 index 0000000..8e0a3b6 --- /dev/null +++ b/lib/services/local_notification_service.dart @@ -0,0 +1,32 @@ +// import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +// import 'package:rxdart/rxdart.dart'; +// +// class LocalNotification { +// static final _notification = FlutterLocalNotificationsPlugin(); +// static final onNotification = BehaviorSubject<String?>(); +// static Future _notificationDetails() async{ +// return NotificationDetails( +// android: AndroidNotificationDetails( +// 'channel id', +// 'channel name', +// 'channel description', +// importance: Importance.max +// ) +// ,iOS: IOSNotificationDetails() +// ); +// } +// static Future init({bool initScheduled = false}) async{ +// final android = AndroidInitializationSettings('@mipmap/ic_launcher'); +// final iOS = IOSInitializationSettings(); +// final settings = InitializationSettings(android: android ,iOS: iOS); +// +// await _notification.initialize(settings, onSelectNotification: (payload) async{onNotification.add(payload);}); +// } +// static Future showNotification({ +// int id = 0, +// String? title, +// String? body, +// String? payload, +// }) async => +// _notification.show(id, title, body, await _notificationDetails(), payload: payload); +// } \ No newline at end of file diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart new file mode 100644 index 0000000..4c4dd14 --- /dev/null +++ b/lib/services/notification_service.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; + +import 'package:initial_folder/helper/user_info.dart'; +import 'package:http/http.dart' as http; +import 'package:initial_folder/models/notification.dart'; +import '../base_service.dart'; + +class NotificationServices { + Future getNotification() async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse("$baseUrl/homepage/list_notification"); + var response = await http.get(url, headers: headerWithToken(token!)); + if (response.statusCode == 200) { + var dataUsersQNA = jsonDecode(response.body)['data']['users']['qna']; + var dataUsersCourses = + jsonDecode(response.body)['data']['users']['courses']; + var dataUsersAnnouncement = + jsonDecode(response.body)['data']['users']['announcement']; + var dataInstructorQNA = + jsonDecode(response.body)['data']['instructur']['qna']; + var dataInstructorCourses = + jsonDecode(response.body)['data']['instructur']['courses_added']; + var dataInstructorAnnouncement = + jsonDecode(response.body)['data']['instructur']['announcement']; + List<NotificationData> data = []; + List<NotificationDataAnnouncementUser> dataAnnouncement = []; + for (var item in dataUsersQNA) { + data.add(NotificationData.qnaFromJson(item)); + } + for (var item in dataUsersCourses) { + data.add(NotificationData.coursesFromJson(item)); + } + // for (var item in dataUsersAnnouncement) { + // data.add(NotificationData.announcementFromJson(item)); + // } + for (var item in dataInstructorQNA) { + data.add(NotificationData.qnaFromJson(item)); + } + for (var item in dataInstructorCourses) { + data.add(NotificationData.coursesFromJson(item)); + } + // for (var item in dataInstructorAnnouncement) { + // data.add(NotificationData.announcementFromJson(item)); + // } + return [data, dataAnnouncement]; + } else { + throw Exception('Data Kursus Teratas Gagal Diambil'); + } + } + + Future readAllNotification() async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse("$baseUrl/homepage/mark_as_read"); + var response = await http.patch(url, headers: headerWithToken(token)); + } + + Future readNotification(String idRead, String ket) async { + Uri url = + Uri.parse("$baseUrl/homepage/read/notification?id=$idRead&ket=$ket"); + // var body = jsonEncode({"id": int.parse(idRead), "ket": ket}); + var response = await http.patch(url); + } + + Future countNotification() async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse("$baseUrl/homepage/list_notification"); + var response = await http.get(url, headers: headerWithToken(token!)); + return jsonDecode(response.body)['data']['count_is_read']; + } +} diff --git a/lib/services/payment_service.dart b/lib/services/payment_service.dart new file mode 100644 index 0000000..2e02638 --- /dev/null +++ b/lib/services/payment_service.dart @@ -0,0 +1,117 @@ +import 'dart:convert'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:http/http.dart' as http; +import 'package:initial_folder/models/zero_price_model.dart'; + +class PaymentServices { + Uri _freeUrl = Uri.parse('$baseUrl/users/payment/free-course'); + Uri _freeUrlCoupon = Uri.parse('$baseUrl/users/coupon-payment-free'); + Uri _paidurl = Uri.parse("$baseUrl/payment/charge"); + Uri _zeroPay = Uri.parse("$baseUrl/payment/zero-price-payment"); + + Future<Map<String, String>> getSnapTransactionToken({ + required String orderId, + required int grossAmount, + required List<Map<String, dynamic>> dataInvoice, + }) async { + String? token = await UsersInfo().getToken(); + + var headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $token', + }; + + var body = jsonEncode({ + "code_coupon": null, + "code_referal": null, + "data_invoice": dataInvoice, + "total_payment": grossAmount, + }); + + print("Request Body: $body"); + + var response = await http.post(_paidurl, headers: headers, body: body); + + if (response.statusCode == 201) { + var responseData = jsonDecode(response.body); + String transactionToken = responseData['token']; + String redirectUrl = responseData['redirect_url']; + + return { + 'transactionToken': transactionToken, + 'redirect_url': redirectUrl, + }; + } else { + print("Gagal mendapatkan token transaksi: ${response.body}"); + throw Exception('Gagal mendapatkan token transaksi'); + } + } + + Future<bool> freeCoure(int idCourse) async { + String? token = await UsersInfo().getToken(); + + var body = jsonEncode({ + "course_id": idCourse, + }); + + http.Response response = await http.post(_freeUrl, headers: headerWithToken(token!), body: body); + print(response.body); + + if (response.statusCode == 201) { + print("Berhasil bayar kursus gratis: ${response.body}"); + return true; + } else if (response.statusCode == 400) { + print("Gagal bayar gratis: ${response.body}"); + return false; + } else { + throw Exception('Gagal Membeli kursus'); + } + } + + Future<bool> freeCoureCoupon(int idCourse, String coupon) async { + String? token = await UsersInfo().getToken(); + + var body = jsonEncode({ + "course_id": idCourse, + "coupon": coupon, + }); + + http.Response response = await http.post(_freeUrlCoupon, headers: headerWithToken(token!), body: body); + + if (response.statusCode == 201) { + print("Berhasil bayar kupon gratis: ${response.body}"); + return true; + } else if (response.statusCode == 400) { + print("Gagal bayar kupon gratis: ${response.body}"); + return false; + } else { + throw Exception('Gagal bayar kupon gratis'); + } + } + + Future<List<ZeroPrice>> zeroPayment({ + required List<Map<String, String>> dataInvoice, + required String totalPayment, + }) async { + String? token = await UsersInfo().getToken(); + + var body = jsonEncode({ + "data_invoice": dataInvoice, + "total_payment": totalPayment, + }); + + http.Response response = await http.post(_zeroPay, headers: headerWithToken(token!), body: body); + print(response.body); + + List<ZeroPrice> zeroPrice = []; + if (response.statusCode == 201) { + zeroPrice.add(ZeroPrice.fromJson(jsonDecode(response.body))); + return zeroPrice; + } else if (response.statusCode == 404) { + return []; + } else { + throw Exception('Gagal Mendapatkan kursus'); + } + } +} diff --git a/lib/services/profile_image_service.dart b/lib/services/profile_image_service.dart new file mode 100644 index 0000000..ba26f59 --- /dev/null +++ b/lib/services/profile_image_service.dart @@ -0,0 +1,30 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/profile_image_post_model.dart'; +import 'package:http/http.dart' as http; + +class ProfileImageService { + Future<ProfileImagePostModel> addProfileImage({required File pckFile}) async { + int? idUser = await UsersInfo().getIdUser(); + Uri url = Uri.parse('$baseUrl/users/profile/user-photo/$idUser'); + String? token = await UsersInfo().getToken(); + + var req = http.MultipartRequest('POST', url); + req.headers.addAll(headerWithToken(token!)); + req.files.add(http.MultipartFile( + 'foto_profile', pckFile.readAsBytes().asStream(), pckFile.lengthSync(), + filename: pckFile.path.split("/").last)); + + var streamed = await req.send(); + var response = await http.Response.fromStream(streamed); + print('Ini file gambar nya ${pckFile.path.split("/").last}'); + print(response.body); + if (response.statusCode == 201 || response.statusCode == 200) { + return ProfileImagePostModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal Mengganti Foto Profil'); + } + } +} diff --git a/lib/services/qna_service.dart b/lib/services/qna_service.dart new file mode 100644 index 0000000..53023bc --- /dev/null +++ b/lib/services/qna_service.dart @@ -0,0 +1,229 @@ +import 'dart:convert'; + +import 'package:http/http.dart' as http; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/counter_qna_comment_model.dart'; +import 'package:initial_folder/models/counter_qna_like_model.dart'; +import 'package:initial_folder/models/qna_model.dart'; + +class QnaService { + //get QNA user + Future<QnaModel> getMyQna(String idCourse) async { + String? token = await UsersInfo().getToken(); + var headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer $token', + }; + Uri url = Uri.parse('$baseUrl/users/qna/$idCourse'); + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + // print(response.body); + if (response.statusCode == 200) { + return QnaModel.fromJson(jsonDecode(response.body)); + } else if (response.statusCode == 404) { + return QnaModel(status: 404, error: true, data: [[]]); + } else { + throw Exception('Data QNA, Gagal Diambil'); + } + } + + // Post QNA User + Future<bool> postingQna( + String title, + String quest, + String idCourse, + String idLesson, + ) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/qna'); + var body = jsonEncode({ + "sender": idUser.toString(), + "title": title, + "quest": quest, + "id_lesson": idLesson, + "id_course": idCourse, + }); + print(body); + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + print(response.body); + + if (response.statusCode == 201) { + return true; + } else { + return false; + } + } + + // Update QNA User + Future<bool> updateQna(String idCourse, String quest, int id_qna, String title, String idLesson) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/qna/$id_qna'); + var body = jsonEncode({ + // "sender": idUser.toString(), + "sender": idUser, + "quest": quest, + "title": title, + "id_lesson": idLesson, + "id_course": idCourse, + }); + print("ini Request Body update qna: $body"); + http.Response response = + await http.put(url, headers: headerWithToken(token!), body: body); + print(response.body); + + if (response.statusCode == 200) { + print(response.body); + print('berhasil update'); + return true; + } else { + return false; + } + } + + //Post Reply QNA User + Future<bool> postQnaReply(String text_rep, String id_qna) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/qna/reply'); + var body = jsonEncode({ + // "sender": idUser.toString(), + "text_rep": text_rep, + "id_qna": id_qna, + }); + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + print(response.statusCode); + + if (response.statusCode == 201) { + return true; + } else { + return false; + } + } + + //Edit Reply QNA User + Future<bool> editQnaReply(int id_rep, String text_rep, String id_qna) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/qna/reply/$id_rep'); + var body = jsonEncode({ + // "sender": idUser.toString(), + "text_rep": text_rep, + "id_qna": id_qna, + }); + http.Response response = + await http.put(url, headers: headerWithToken(token!), body: body); + print(response.body); + + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } + + // Delete Qna (Pertanyaan User) + Future<bool> deleteQna( + int id_qna, + ) async { + Uri url = Uri.parse('$baseUrl/users/qna/$id_qna'); + String? token = await UsersInfo().getToken(); + print(token); + + http.Response response = await http.delete( + url, + headers: headerWithToken(token!), + ); + print(response.body); + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } + + // Delete Reply Qna User + Future<bool> deleteReplyQna( + int id_rep, + ) async { + Uri url = Uri.parse('$baseUrl/users/qna/reply/$id_rep'); + String? token = await UsersInfo().getToken(); + print(token); + + http.Response response = await http.delete( + url, + headers: headerWithToken(token!), + ); + print(response.body); + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } + + //get counter comment qna + Future<CounterCommentModel> getCounterComment(String idQna) async { + String? token = await UsersInfo().getToken(); + + Uri url = Uri.parse('$baseUrl/users/qna/count/$idQna'); + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + print(response.body); + + if (response.statusCode == 200) { + return CounterCommentModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Data Counter Comment QNA, Gagal Diambil'); + } + } + + // Like or Unlike + Future<bool> likeOrLike(int idQna) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/qna/up/$idQna'); + // var body = jsonEncode({"user_id": idUser.toString()}); + // http.Response response = + // await http.post(url, headers: headerWithToken(token!), body: body); + http.Response response = + await http.post(url, headers: headerWithToken(token!)); + print(response.body); + + if (response.statusCode == 200) { + return true; + } else if (response.statusCode == 400) { + Uri url = Uri.parse('$baseUrl/users/qna/unup/$idQna'); + http.Response response = + await http.post(url, headers: headerWithToken(token!)); + + if (response.statusCode == 200) { + return true; + } else { + return false; + } + } else { + return false; + } + } + + //get counter comment qna + Future<CounterLikeModel> getCounterLike(String idQna) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/qna/like/$idQna'); + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + print(response.body); + + if (response.statusCode == 200) { + return CounterLikeModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Data Counter Like QNA, Gagal Diambil'); + } + } +} diff --git a/lib/services/quiz_service.dart b/lib/services/quiz_service.dart new file mode 100644 index 0000000..47f88fb --- /dev/null +++ b/lib/services/quiz_service.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'package:http/http.dart' as http; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/quiz_model.dart'; +import 'package:initial_folder/models/quiz_perquestion_result_model.dart'; +import 'package:initial_folder/models/quiz_question_model.dart'; +import 'package:initial_folder/models/quiz_question_result_model.dart'; + +class quiz_service { + Future get_quiz_info(String id) async { + Uri url = Uri.parse('$baseUrl/users/quiz/question/$id'); + String? token = await UsersInfo().getToken(); + var response; + if (token == null) { + response = await http.get(url, headers: baseHeader); + } else { + response = await http.get(url, headers: headerWithToken(token)); + } + log(response.body); + if (response.statusCode == 200) { + final data = quizModelFromJson(response.body); + return data; + } else { + throw Exception('Data Detail Kursus Gagal Diambil'); + } + } + + Future get_result_quiz(String resultArr) async { + Uri url = Uri.parse('$baseUrl/users/quiz/question/check-answer'); + String? token = await UsersInfo().getToken(); + var response; + // print(resultArr + "ini pas ngambil"); + if (token == null) { + response = await http.get(url, headers: baseHeader); + } else { + print("disaodji" + resultArr); + response = await http.post(url, + headers: headerWithToken(token), body: resultArr); + } + if (response.statusCode == 200) { + final data = quizQuestionResultFromJson(response.body); + log(data.toString()); + return data; + + // return jsonData; + } else { + print("errrorr---------" + response.statusCode); + throw Exception('Data Detail jawaban Gagal Diambil'); + } + } + + Future get_result_quiz_pernumber(int quizId, int index) async { + Uri url = + Uri.parse('$baseUrl/users/quiz/question/${quizId}?slice=${index}'); + String? token = await UsersInfo().getToken(); + var response; + // print(resultArr + "ini pas ngambil"); + if (token == null) { + response = await http.get(url, headers: baseHeader); + } else { + // print("disaodji" + resultArr); + response = await http.get(url, headers: headerWithToken(token)); + } + if (response.statusCode == 200) { + final data = quizPerQuestionResultFromJson(response.body); + log(data.data.first.toString()); + return data; + + // return jsonData; + } else { + print("errrorr---------" + response.statusCode); + throw Exception('Data Detail jawaban Gagal Diambil'); + } + } +} diff --git a/lib/services/reset_service.dart b/lib/services/reset_service.dart new file mode 100644 index 0000000..c780702 --- /dev/null +++ b/lib/services/reset_service.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/reset_model.dart'; +import 'package:http/http.dart' as http; + +class ResetService { + Future<ResetModel> kirimEmail({ + required String email, + }) async { + Uri url = Uri.parse('$baseUrl/auth/web/forgot-password'); + var body = jsonEncode({ + 'email': email, + }); + + print('Request URL: $url'); + print('Request Headers: $baseHeader'); + print('Request Body: $body'); + + var response = await http.post(url, headers: baseHeader, body: body); + if (response.statusCode == 200) { + print('Berhasil kirim link reset password ke email nih :'); + print(response.statusCode); + return ResetModel.fromJson(jsonDecode(response.body)['data']); + } else { + print(response.statusCode); + print(response.request); + print(response.reasonPhrase); + throw Exception('Gagal Kirim Email'); + ; + } + } +} diff --git a/lib/services/search_service.dart b/lib/services/search_service.dart new file mode 100644 index 0000000..14ce3e8 --- /dev/null +++ b/lib/services/search_service.dart @@ -0,0 +1,70 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/course_model.dart'; +import 'package:http/http.dart' as http; +import 'dart:developer'; + +class SearchService { + Future<List<CourseModel>> search(String? judul) async { + Uri url = Uri.parse('$baseUrl/homepage/filter?keyword=$judul'); + http.Response response = await http.get(url, headers: baseHeader); + log(response.body); + if (response.statusCode == 200) { + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> course = []; + for (var item in data) { + course.add(CourseModel.fromJson(item)); + } + + return course; + } else { + throw Exception('Gagal'); + } + } + + Future<dynamic> filter({ + String price = '', + String level = '', + String language = '', + String rating = '', + String keyword = '', + }) async { + Map<String, dynamic> queryParams = { + if (keyword != '') 'keyword': keyword, + if (price != '') 'price': price, + if (level != '') 'level': level, + if (language != '') 'language': language, + if (rating != '') 'rating': rating, + }; + print("Isi Query Form ---------> $queryParams"); + Uri url = Uri.parse('$baseUrl/homepage/course/search').replace( + queryParameters: queryParams, + ); + if (queryParams.containsKey('keyword')) { + url = Uri.parse('$baseUrl/homepage/course/search').replace( + queryParameters: queryParams, + ); + } else if (price != '') { + url = Uri.parse('$baseUrl/homepage/course/search').replace( + queryParameters: queryParams, + ); + } + + print("Url to API ----> $url"); + var response = + await http.get(Uri.parse(Uri.decodeComponent(url.toString()))); + if (response.statusCode == 200) { + print("berhasil fitlter search ${response.body}"); + var data = jsonDecode(response.body)['data'][0]; + List<CourseModel> courses = []; + for (var item in data) { + courses.add(CourseModel.fromJson(item)); + } + return courses; + } else { + print("gagal fitlter search ${response.body}"); + throw Exception('Gagal'); + } + } +} diff --git a/lib/services/section_lesson_service.dart b/lib/services/section_lesson_service.dart new file mode 100644 index 0000000..4e97908 --- /dev/null +++ b/lib/services/section_lesson_service.dart @@ -0,0 +1,18 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/section_lesson_model.dart'; +import 'package:http/http.dart' as http; + +class SectionLessonService { + Future<SectionLessonModel> getSectionLessonCourse(String id) async { + Uri url = Uri.parse('$baseUrl/homepage/section/$id'); + http.Response response = await http.get(url, headers: baseHeader); + print(response.statusCode); + if (response.statusCode == 200) { + return SectionLessonModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Data Detail Kursus Gagal Diambil'); + } + } +} diff --git a/lib/services/user_info_service.dart b/lib/services/user_info_service.dart new file mode 100644 index 0000000..4531b1f --- /dev/null +++ b/lib/services/user_info_service.dart @@ -0,0 +1,195 @@ +import 'dart:convert'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/update_incomplete_profile_model.dart'; +import 'package:initial_folder/models/update_data_diri_model.dart'; +import 'package:initial_folder/models/user_info_incomplete_model.dart'; +import 'package:initial_folder/models/user_info_model.dart'; +import 'package:initial_folder/models/data_diri_model.dart'; +import 'package:http/http.dart' as http; + +class UserInfoService { + Future<UserInfoModel> getUserInfo(email) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/auth/me?email=$email'); + + var response = await http.get(url, headers: headerWithToken(token!)); + + print(response.body); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + UserInfoModel userInfoModel = UserInfoModel.fromJson(data); + await UsersInfo() + .setIdUser(int.tryParse(userInfoModel.data[0].idUser ?? '')); + return userInfoModel; + } else if (response.statusCode == 202) { + var data = jsonDecode(response.body); + UserInfoModel userInfoModel = UserInfoModel.fromJson(data); + await UsersInfo() + .setIdUser(int.tryParse(userInfoModel.data[0].idUser ?? '')); + return userInfoModel; + } else { + throw Exception('Gagal ambil data'); + } + } + + Future<DataDiriModel> getDataDiri() async { + int? id = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/users-info/$id'); + + var response = await http.get(url, headers: headerWithToken(token!)); + + if (response.statusCode == 200) { + return DataDiriModel.fromJson(jsonDecode(response.body)); + } else if (response.statusCode == 202) { + return DataDiriModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal ambil data'); + } + } + + Future getDataDiriADMIN() async { + int? id = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/users-info/$id'); + + var response; + + if (token != null) { + response = await http.get(url, headers: headerWithToken(token)); + } else { + response = await http.get(url); + } + + if (response.statusCode == 401) { + return response.statusCode; + } else if (response.statusCode == 200) { + return response.statusCode; + } else { + return response.statusCode; + } + } + + Future<UpdateDataDiriModel> updateDataDiri({ + String? fullname, + String? biograph, + String? phone, + String? email, + String? twitter, + String? facebook, + String? linkedin, + String? instagram, + String? datebirth, + String? gender, + String? headline, + }) async { + int? id = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/profile/user-profile/$id'); + + var body = jsonEncode({ + "full_name": fullname, + "biography": biograph, + "phone": phone, + "email": email, + // "datebirth": datebirth, + // "jenis_kel": gender, + "headline": headline, + "social_link": { + "twitter": twitter, + "facebook": facebook, + "linkedin": linkedin, + "instagram": instagram + } + }); + var response = + await http.put(url, headers: headerWithToken(token!), body: body); + if (response.statusCode == 200) { + return UpdateDataDiriModel.fromJson(jsonDecode(response.body)); + } else if (response.statusCode == 403) { + return UpdateDataDiriModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal update data'); + } + } + + Future<UpdateIncompleteProfileModel> updateIncompleteProfile({ + String? fullname, + String? phone, + String? email, + String? newPassword, + String? newConfirmPassword, + }) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/profile-completion'); + + var body = jsonEncode({ + "phone": phone, + "password": newPassword, + "confirm_password": newConfirmPassword, + }); + var response = + await http.put(url, headers: headerWithToken(token!), body: body); + if (response.statusCode == 200 || + response.statusCode == 400 || + response.statusCode == 404) { + return UpdateIncompleteProfileModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception( + 'Gagal update data: update incomplete profile: ${response.body}'); + } + } + + Future<UserInfoIncompleteModel> getUserInfoIncomplete() async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/users-info'); + + var response = await http.get(url, headers: headerWithToken(token!)); + if (response.statusCode == 200) { + UserInfoIncompleteModel userInfoIncompleteModel = + UserInfoIncompleteModel.fromJson(jsonDecode(response.body)); + return userInfoIncompleteModel; + } else if (response.statusCode == 202) { + UserInfoIncompleteModel userInfoIncompleteModel = + UserInfoIncompleteModel.fromJson(jsonDecode(response.body)); + + return userInfoIncompleteModel; + } else { + throw Exception('Gagal ambil data'); + } + } + + Future updatePassword( + {required idUser, + required String? email, + required String? oldPassword, + required String? password, + required String? newPasswordConfirm}) async { + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/profile/user-credentials/$idUser'); + + var body = jsonEncode({ + 'email': email, + 'old_password': oldPassword, + "password": password, + "new_password_confirm": newPasswordConfirm, + }); + + var response = + await http.put(url, headers: headerWithToken(token!), body: body); + + if (response.statusCode == 200) { + var data = jsonDecode(response.body); + var status = data['status']; + if (status == 200) { + return; + } else { + throw Exception('Gagal update password'); + } + } else { + throw Exception('Gagal update password'); + } + } +} diff --git a/lib/services/voucher_service.dart b/lib/services/voucher_service.dart new file mode 100644 index 0000000..f7ba587 --- /dev/null +++ b/lib/services/voucher_service.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:http/http.dart' as http; +import 'package:initial_folder/models/voucher_model.dart'; + +class VoucherService { + Future<VoucherModel> radeemVoucher(int? idCourse, String voucher) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse( + '$baseUrl/users/coupon-payment?voucher=$voucher&course_id=$idCourse'); + + http.Response response = + await http.get(url, headers: headerWithToken(token!)); + print("Ini API nya ${url}"); + if (response.statusCode == 200) { + print("Berhasil kupon ${response.body}"); + return VoucherModel.fromJson(jsonDecode(response.body)); + } else { + print("gagal kupon ${response.body}"); + throw Exception('Gagal redeem voucher'); + } + } + + Future<VoucherModel> redeemVoucherCart( + List<String> idCourse, String voucher) async { + int? idUser = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$baseUrl/users/voucher-cart'); + var body = jsonEncode({ + // "user_id": idUser, + "courses_id": idCourse, + "voucher": voucher, + }); + + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + if (response.statusCode == 201) { + print("radeem voucher cart ${response.body}"); + return VoucherModel.fromJson(jsonDecode(response.body)); + } else { + print("gagal radeem voucher cart ${response.body}"); + throw Exception('Gagal redeem voucher'); + } + } + + Future<VoucherModel> cancelCoupon(coupon) async { + Uri url = Uri.parse('$baseUrl/users/cancel-redeem-voucher/$coupon'); + String? token = await UsersInfo().getToken(); + http.Response response = + await http.delete(url, headers: headerWithToken(token!)); + print(response.body); + if (response.statusCode == 200) { + print("cancel coupon ${response.body}"); + return VoucherModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal Cancel Coupon'); + } + } +} diff --git a/lib/services/wishlist_service.dart b/lib/services/wishlist_service.dart new file mode 100644 index 0000000..fc078e1 --- /dev/null +++ b/lib/services/wishlist_service.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/wishlist_model.dart'; +import 'package:http/http.dart' as http; + +class WishlistService { + var _baseUrl = '$baseUrl/users/wishlist'; + Future<WishlistPostModel> addWishlist( + int wishlistItem, + ) async { + Uri url = Uri.parse(_baseUrl); + String? token = await UsersInfo().getToken(); + int? idUser = await UsersInfo().getIdUser(); + + var body = jsonEncode({ + // 'id_user': idUser, + 'wishlist_item': wishlistItem, + }); + http.Response response = + await http.post(url, headers: headerWithToken(token!), body: body); + print(response.body); + if (response.statusCode == 201 || response.statusCode == 200) { + return WishlistPostModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal Menambahkan Wishlist'); + } + } + + Future<WishlistPostModel> deleteWishlist( + int wishlistItem, + ) async { + Uri url = Uri.parse('$_baseUrl/delete/$wishlistItem'); + String? token = await UsersInfo().getToken(); + print(token); + + http.Response response = await http.delete( + url, + headers: headerWithToken(token!), + ); + print(response.body); + if (response.statusCode == 200) { + return WishlistPostModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal Menghapus Wishlist'); + } + } + + Future<WishlistModel> getWishlist() async { + int? id = await UsersInfo().getIdUser(); + String? token = await UsersInfo().getToken(); + Uri url = Uri.parse('$_baseUrl?users=$id'); + + http.Response response = await http.get( + url, + headers: headerWithToken(token!), + ); + print(response.body); + if (response.statusCode == 200) { + return WishlistModel.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Gagal Mendapatkan Wishlist'); + } + } +} diff --git a/lib/size_config.dart b/lib/size_config.dart new file mode 100644 index 0000000..61f0263 --- /dev/null +++ b/lib/size_config.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class SizeConfig { + static late MediaQueryData _mediaQueryData; + static late double screenWidth; + static late double screenHeight; + static double? defaultSize; + static Orientation? orientation; + static double? blockHorizontal; + static double? blockVertical; + + void init(BuildContext context) { + _mediaQueryData = MediaQuery.of(context); + screenWidth = _mediaQueryData.size.width; + screenHeight = _mediaQueryData.size.height; + orientation = _mediaQueryData.orientation; + blockHorizontal = screenWidth / 100; + blockVertical = screenHeight / 100; + } +} + +// Get the proportionate height as per screen size +double getProportionateScreenHeight(double inputHeight) { + double screenHeight = SizeConfig.screenHeight; + // 640 is the layout height that designer use + return (inputHeight / 640.0) * screenHeight; +} + +// Get the proportionate height as per screen size +double getProportionateScreenWidth(double inputWidth) { + double screenWidth = SizeConfig.screenWidth; + // 360 is the layout width that designer use + return (inputWidth / 360.0) * screenWidth; +} diff --git a/lib/theme.dart b/lib/theme.dart new file mode 100644 index 0000000..6005e33 --- /dev/null +++ b/lib/theme.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + + +//primary color ligt mode +const Color primaryColorligtmode = Color(0xff161616); +// DARK MODE COLOR +// Kuning +const Color primaryColor = Color(0xffFFAF01); +const Color primaryColordua = Color(0xff25D366); +// Abu +const Color secondaryColor = Color(0xffBFBFBF); +// Cream +const Color thirdColor = Color(0xffECE5DD); +// Abu Gelap +const Color fourthColor = Color(0xff616161); +// Kuning 2 +const Color fiveColor = Color(0xffFED27C); +// Label +const Color sixColor = Color(0xffECEB98); +// Warning +const Color sevenColor = Color(0xffFF502f); +// Success +const Color eightColor = Color(0xff25D366); +// Hitam +const Color ninthColor = Color(0xff404040); +// Putih +const Color tenthColor = Color(0xffF4F4F4); +//Abu Terang Banget +const Color elveColor = Color(0xffDDE5E9); +// Navy +const Color twelveColor = Color(0xff000417); +// Kuning 3 +const Color thirteenColor = Color(0xffEDA923); +// Background +const Color fourteenColor = Color(0xff212121); +// Abu 2 +const Color fifteenColor = Color(0xff4A4D60); +// Background +const Color sixteenColor = Color(0xffF8F8F8); +// Abu text field +const Color seventeenColor = Color(0xff212643); +// text putih +const Color baruTextutih = Color(0xffFFFFFF); + +const Color backgroundColor = Color(0xff181818); +//text #1C1C1C +const Color baruTexthitam = Color(0xff1C1C1C); +//bg textformfield Login + +TextStyle primaryTextStyle = GoogleFonts.notoSans(); +TextStyle secondaryTextStyle = GoogleFonts.openSans(); +TextStyle thirdTextStyle = GoogleFonts.poppins(); + +const FontWeight light = FontWeight.w300; +const FontWeight reguler = FontWeight.w400; +const FontWeight medium = FontWeight.w500; +const FontWeight semiBold = FontWeight.w600; +const FontWeight bold = FontWeight.w700; + +class ThemeClass { + static ThemeData lightmode = ThemeData( + brightness: Brightness.light, + colorScheme: ColorScheme.light( + primary: primaryColor, + secondary: fourteenColor, + background: sixteenColor, + primaryContainer: baruTextutih, + onPrimary: seventeenColor, + onBackground: baruTexthitam, + ), + ); + static ThemeData darkmode = ThemeData( + brightness: Brightness.dark, + colorScheme: ColorScheme.dark( + primary: primaryColordua, + secondary: fifteenColor, + background: twelveColor, + primaryContainer: seventeenColor, + onPrimary: baruTextutih, + onBackground: baruTextutih, + ), + ); +} + +ThemeClass _themeClass = ThemeClass(); diff --git a/lib/widgets/announcement_user.dart b/lib/widgets/announcement_user.dart new file mode 100644 index 0000000..8a32ad4 --- /dev/null +++ b/lib/widgets/announcement_user.dart @@ -0,0 +1,198 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/providers/announcement_provider.dart'; +import 'package:initial_folder/providers/like_announcement.dart'; +import 'package:initial_folder/screens/course/component/inside_announcement.dart'; +import 'package:provider/provider.dart'; + +import '../get_it.dart'; +import '../size_config.dart'; +import '../theme.dart'; + +class AnnouncementUser extends StatefulWidget { + const AnnouncementUser({ + Key? key, + required this.id, + this.divider, + required this.announcementDataModel, + required this.index, + required this.userId, + }) : super(key: key); + + final Widget? divider; + final id; + final AnnouncementDataModel announcementDataModel; + final int index; + final int userId; + + @override + State<AnnouncementUser> createState() => _AnnouncementUserState(); +} + +class _AnnouncementUserState extends State<AnnouncementUser> { + double value = 0; + final provider = announcementGetIt<AnnouncementProvider>(); + + @override + Widget build(BuildContext context) { + LikeOrAnnouncementProvider _likeOrAnnouncementProvider = + Provider.of<LikeOrAnnouncementProvider>(context); + + likeOrAnnouncement(String tokenAnnouncement) async { + final provider = announcementGetIt<AnnouncementProvider>(); + if (await _likeOrAnnouncementProvider + .likeOrAnnouncement(tokenAnnouncement)) { + provider.getAnnouncement(widget.id); + } + } + + return Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InsideAnnouncement( + announcementDataModel: widget.announcementDataModel, + id: widget.id, + index: widget.index, + userId: widget.userId, + ), + ), + ).then((value) => provider.getAnnouncement(widget.id)); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + backgroundColor: primaryColor, + backgroundImage: + widget.announcementDataModel.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage( + widget.announcementDataModel.fotoProfile ?? + '') as ImageProvider, + ), + SizedBox( + width: getProportionateScreenWidth(8), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.announcementDataModel.instructorName ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: + Theme.of(context).colorScheme.onBackground, + ), + ), + ], + ), + Text( + widget.announcementDataModel.date ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).colorScheme.onBackground, + ), + ), + ], + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(9)), + Text( + widget.announcementDataModel.bodyContent ?? '', + style: thirdTextStyle.copyWith( + color: Theme.of(context).colorScheme.onBackground, + letterSpacing: 1, + fontSize: SizeConfig.blockHorizontal! * 3.4, + ), + ), + SizedBox(height: getProportionateScreenHeight(5)), + ], + ), + ), + Row( + children: [ + kLike( + widget.announcementDataModel.isLike == 1 + ? Icons.favorite + : Icons.favorite_border_rounded, + widget.announcementDataModel.isLike == 1 + ? Colors.red + : secondaryColor, () { + likeOrAnnouncement( + widget.announcementDataModel.tokenAnnouncement!); + }), + SizedBox( + width: getProportionateScreenWidth(3), + ), + Text( + "${widget.announcementDataModel.countLike ?? 0}", + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + letterSpacing: 0.3, + ), + ), + SizedBox(width: getProportionateScreenWidth(13)), + kComment(widget.announcementDataModel.replies.length), + ], + ), + SizedBox( + height: getProportionateScreenWidth(13), + ), + SizedBox( + child: widget.divider, + ) + ], + ), + ); + } +} + +Widget kLike(IconData icon, Color color, Function onTap) { + return GestureDetector( + onTap: () => onTap(), + child: Row( + children: [ + Icon( + icon, + color: color, + size: 12, + ), + ], + ), + ); +} + +Widget kComment(int value) { + return Row( + children: [ + Icon( + FontAwesomeIcons.comment, + color: secondaryColor, + size: 12, + ), + SizedBox( + width: getProportionateScreenWidth(3), + ), + Text( + "$value", + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), letterSpacing: 0.3), + ) + ], + ); +} diff --git a/lib/widgets/announcement_user_page.dart b/lib/widgets/announcement_user_page.dart new file mode 100644 index 0000000..9613c90 --- /dev/null +++ b/lib/widgets/announcement_user_page.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/providers/announcement_provider.dart'; +import 'package:initial_folder/widgets/announcement_user.dart'; +import '../get_it.dart'; +import '../theme.dart'; + +class AnnouncementUserPage extends StatefulWidget { + const AnnouncementUserPage({Key? key, required this.idCourse}) + : super(key: key); + final idCourse; + + @override + State<AnnouncementUserPage> createState() => _AnnouncementUserPageState(); +} + +class _AnnouncementUserPageState extends State<AnnouncementUserPage> { + final provider = announcementGetIt<AnnouncementProvider>(); + int? userId = 0; + + void getUserId() async { + userId = await UsersInfo().getIdUser(); + } + + @override + void initState() { + // TODO: implement initState + getUserId(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + provider.getAnnouncement(widget.idCourse); + late Widget build; + + return StreamBuilder<AnnouncementModel>( + stream: provider.announcementStream, + builder: (context, AsyncSnapshot<AnnouncementModel> snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + build = Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + break; + case ConnectionState.none: + build = Center( + child: Text( + 'Tidak ada koneksi', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.active: + if (snapshot.data!.data[0].isEmpty) { + build = Center( + child: Text( + 'Tidak Ada Pengumuman', + style: thirdTextStyle, + ), + ); + } else { + build = ListView.builder( + itemCount: snapshot.data!.data[0].length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemBuilder: (context, index) { + return AnnouncementUser( + divider: Divider(), + announcementDataModel: snapshot.data!.data[0][index], + id: widget.idCourse, + index: index, + userId: userId!, + ); + }, + ); + } + break; + case ConnectionState.done: + if (snapshot.data!.data[0].isEmpty) { + build = Center( + child: Text( + 'Belum ada pengumuman', + style: thirdTextStyle, + ), + ); + } else { + build = ListView.builder( + itemCount: snapshot.data!.data[0].length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemBuilder: (context, index) { + return AnnouncementUser( + divider: Divider(), + announcementDataModel: snapshot.data!.data[0][index], + id: widget.idCourse, + index: index, + userId: userId!, + ); + }, + ); + } + break; + } + } + return build; + }, + ); + } +} diff --git a/lib/widgets/counter_qna_comment.dart b/lib/widgets/counter_qna_comment.dart new file mode 100644 index 0000000..5cdf2bc --- /dev/null +++ b/lib/widgets/counter_qna_comment.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/counter_qna_comment_model.dart'; + +import '../size_config.dart'; +import '../theme.dart'; + +class CounterQnaComment extends StatelessWidget { + const CounterQnaComment({Key? key, required this.counterComment}) + : super(key: key); + + final String counterComment; + @override + Widget build(BuildContext context) { + return Text( + '${counterComment}', + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), letterSpacing: 0.3), + ); + } +} diff --git a/lib/widgets/counter_qna_comment_page.dart b/lib/widgets/counter_qna_comment_page.dart new file mode 100644 index 0000000..7aa0218 --- /dev/null +++ b/lib/widgets/counter_qna_comment_page.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/counter_qna_comment_provider.dart'; +import 'package:initial_folder/widgets/counter_qna_comment.dart'; +import 'package:provider/provider.dart'; + +import '../theme.dart'; + +class CounterQnaCommentPage extends StatelessWidget { + const CounterQnaCommentPage({Key? key, required this.idQna}) + : super(key: key); + + final idQna; + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => CounterQnaCommentProvider(idQna: idQna), + child: Consumer<CounterQnaCommentProvider>(builder: (context, state, _) { + if (state.state == ResultState.loading) { + print(idQna); + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.noData) { + return Center( + child: Text( + 'TIDAK ADA', + style: thirdTextStyle, + ), + ); + } else if (state.state == ResultState.hasData) { + var counterQna = state.result!.data; + + return CounterQnaComment( + counterComment: counterQna!, + ); + // var counterQna = state.result!.data; + // return CounterQnaComment( + // counterComment: counterQna, + // ); + } else if (state.state == ResultState.error) { + return Center( + child: Column( + children: [ + Text( + 'Terjadi Kesalahan Coba Lagi', + style: thirdTextStyle, + ), + ], + )); + } + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + }), + ); + } +} diff --git a/lib/widgets/counter_qna_like.dart b/lib/widgets/counter_qna_like.dart new file mode 100644 index 0000000..8a8fd69 --- /dev/null +++ b/lib/widgets/counter_qna_like.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/like_or_unlike_provider.dart'; +import 'package:provider/provider.dart'; + +import '../size_config.dart'; +import '../theme.dart'; + +class CounterQnaLike extends StatelessWidget { + const CounterQnaLike({ + Key? key, + required this.counterLike, + }) : super(key: key); + + final counterLike; + + @override + Widget build(BuildContext context) { + return Text( + '${counterLike}', + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), letterSpacing: 0.3), + ); + } +} diff --git a/lib/widgets/counter_qna_like_page.dart b/lib/widgets/counter_qna_like_page.dart new file mode 100644 index 0000000..deaeb12 --- /dev/null +++ b/lib/widgets/counter_qna_like_page.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/counter_qna_like_provider.dart'; +import 'package:initial_folder/widgets/counter_qna_like.dart'; +import 'package:provider/provider.dart'; + +import '../theme.dart'; + +class CounterQnaLikePage extends StatelessWidget { + const CounterQnaLikePage({Key? key, required this.idQna}) : super(key: key); + + final idQna; + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => CounterQnaLikeProvider(idQna: idQna), + child: Consumer<CounterQnaLikeProvider>(builder: (context, state, _) { + if (state.state == ResultState.loading) { + print(idQna); + return Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + } else if (state.state == ResultState.noData) { + return Center( + child: Text( + 'TIDAK ADA', + style: thirdTextStyle, + ), + ); + } else if (state.state == ResultState.hasData) { + var counterQna = state.result!.data; + + return CounterQnaLike( + counterLike: counterQna, + ); + // var counterQna = state.result!.data; + // return CounterQnaComment( + // counterComment: counterQna, + // ); + } else if (state.state == ResultState.error) { + return Center( + child: Column( + children: [ + Text( + 'Terjadi Kesalahan Coba Lagi', + style: thirdTextStyle, + ), + ], + )); + } + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + }), + ); + } +} diff --git a/lib/widgets/custom_expansion_tile.dart b/lib/widgets/custom_expansion_tile.dart new file mode 100644 index 0000000..330cd64 --- /dev/null +++ b/lib/widgets/custom_expansion_tile.dart @@ -0,0 +1,376 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +const Duration _kExpand = Duration(milliseconds: 200); + +/// A single-line [ListTile] with an expansion arrow icon that expands or collapses +/// the tile to reveal or hide the [children]. +/// +/// This widget is typically used with [ListView] to create an +/// "expand / collapse" list entry. When used with scrolling widgets like +/// [ListView], a unique [PageStorageKey] must be specified to enable the +/// [CustomExpansionTile] to save and restore its expanded state when it is scrolled +/// in and out of view. +/// +/// This class overrides the [ListTileThemeData.iconColor] and [ListTileThemeData.textColor] +/// theme properties for its [ListTile]. These colors animate between values when +/// the tile is expanded and collapsed: between [iconColor], [collapsedIconColor] and +/// between [textColor] and [collapsedTextColor]. +/// +/// The expansion arrow icon is shown on the right by default in left-to-right languages +/// (i.e. the trailing edge). This can be changed using [controlAffinity]. This maps +/// to the [leading] and [trailing] properties of [CustomExpansionTile]. +/// +/// {@tool dartpad} +/// This example demonstrates different configurations of CustomExpansionTile. +/// +/// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [ListTile], useful for creating expansion tile [children] when the +/// expansion tile represents a sublist. +/// * The "Expand and collapse" section of +/// <https://material.io/components/lists#types> +class CustomExpansionTile extends StatefulWidget { + /// Creates a single-line [ListTile] with an expansion arrow icon that expands or collapses + /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must + /// be non-null. + const CustomExpansionTile({ + Key? key, + this.leading, + required this.title, + this.subtitle, + this.onExpansionChanged, + this.children = const <Widget>[], + this.initiallyExpanded = false, + this.maintainState = false, + this.tilePadding, + this.expandedCrossAxisAlignment, + this.expandedAlignment, + this.childrenPadding, + this.backgroundColor, + this.collapsedBackgroundColor, + this.textColor, + this.collapsedTextColor, + this.iconColor, + this.collapsedIconColor, + this.controlAffinity, + }) : assert(initiallyExpanded != null), + assert(maintainState != null), + assert( + expandedCrossAxisAlignment != CrossAxisAlignment.baseline, + 'CrossAxisAlignment.baseline is not supported since the expanded children ' + 'are aligned in a column, not a row. Try to use another constant.', + ), + super(key: key); + + /// A widget to display before the title. + /// + /// Typically a [CircleAvatar] widget. + /// + /// Note that depending on the value of [controlAffinity], the [leading] widget + /// may replace the rotating expansion arrow icon. + final Widget? leading; + + /// The primary content of the list item. + /// + /// Typically a [Text] widget. + final Widget title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + final Widget? subtitle; + + /// Called when the tile expands or collapses. + /// + /// When the tile starts expanding, this function is called with the value + /// true. When the tile starts collapsing, this function is called with + /// the value false. + final ValueChanged<bool>? onExpansionChanged; + + /// The widgets that are displayed when the tile expands. + /// + /// Typically [ListTile] widgets. + final List<Widget> children; + + /// The color to display behind the sublist when expanded. + final Color? backgroundColor; + + /// When not null, defines the background color of tile when the sublist is collapsed. + final Color? collapsedBackgroundColor; + + /// A widget to display after the title. + /// + /// Note that depending on the value of [controlAffinity], the [trailing] widget + /// may replace the rotating expansion arrow icon. + + /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). + final bool initiallyExpanded; + + /// Specifies whether the state of the children is maintained when the tile expands and collapses. + /// + /// When true, the children are kept in the tree while the tile is collapsed. + /// When false (default), the children are removed from the tree when the tile is + /// collapsed and recreated upon expansion. + final bool maintainState; + + /// Specifies padding for the [ListTile]. + /// + /// Analogous to [ListTile.contentPadding], this property defines the insets for + /// the [leading], [title], [subtitle] and [trailing] widgets. It does not inset + /// the expanded [children] widgets. + /// + /// When the value is null, the tile's padding is `EdgeInsets.symmetric(horizontal: 16.0)`. + final EdgeInsetsGeometry? tilePadding; + + /// Specifies the alignment of [children], which are arranged in a column when + /// the tile is expanded. + /// + /// The internals of the expanded tile make use of a [Column] widget for + /// [children], and [Align] widget to align the column. The `expandedAlignment` + /// parameter is passed directly into the [Align]. + /// + /// Modifying this property controls the alignment of the column within the + /// expanded tile, not the alignment of [children] widgets within the column. + /// To align each child within [children], see [expandedCrossAxisAlignment]. + /// + /// The width of the column is the width of the widest child widget in [children]. + /// + /// When the value is null, the value of `expandedAlignment` is [Alignment.center]. + final Alignment? expandedAlignment; + + /// Specifies the alignment of each child within [children] when the tile is expanded. + /// + /// The internals of the expanded tile make use of a [Column] widget for + /// [children], and the `crossAxisAlignment` parameter is passed directly into the [Column]. + /// + /// Modifying this property controls the cross axis alignment of each child + /// within its [Column]. Note that the width of the [Column] that houses + /// [children] will be the same as the widest child widget in [children]. It is + /// not necessarily the width of [Column] is equal to the width of expanded tile. + /// + /// To align the [Column] along the expanded tile, use the [expandedAlignment] property + /// instead. + /// + /// When the value is null, the value of `expandedCrossAxisAlignment` is [CrossAxisAlignment.center]. + final CrossAxisAlignment? expandedCrossAxisAlignment; + + /// Specifies padding for [children]. + /// + /// When the value is null, the value of `childrenPadding` is [EdgeInsets.zero]. + final EdgeInsetsGeometry? childrenPadding; + + /// The icon color of tile's expansion arrow icon when the sublist is expanded. + /// + /// Used to override to the [ListTileThemeData.iconColor]. + final Color? iconColor; + + /// The icon color of tile's expansion arrow icon when the sublist is collapsed. + /// + /// Used to override to the [ListTileThemeData.iconColor]. + final Color? collapsedIconColor; + + /// The color of the tile's titles when the sublist is expanded. + /// + /// Used to override to the [ListTileThemeData.textColor]. + final Color? textColor; + + /// The color of the tile's titles when the sublist is collapsed. + /// + /// Used to override to the [ListTileThemeData.textColor]. + final Color? collapsedTextColor; + + /// Typically used to force the expansion arrow icon to the tile's leading or trailing edge. + /// + /// By default, the value of `controlAffinity` is [ListTileControlAffinity.platform], + /// which means that the expansion arrow icon will appear on the tile's trailing edge. + final ListTileControlAffinity? controlAffinity; + + @override + State<CustomExpansionTile> createState() => _CustomExpansionTileState(); +} + +class _CustomExpansionTileState extends State<CustomExpansionTile> + with SingleTickerProviderStateMixin { + static final Animatable<double> _easeOutTween = + CurveTween(curve: Curves.easeOut); + static final Animatable<double> _easeInTween = + CurveTween(curve: Curves.easeIn); + static final Animatable<double> _halfTween = + Tween<double>(begin: 0.0, end: 0.5); + + final ColorTween _borderColorTween = ColorTween(); + final ColorTween _headerColorTween = ColorTween(); + final ColorTween _iconColorTween = ColorTween(); + final ColorTween _backgroundColorTween = ColorTween(); + + late AnimationController _controller; + late Animation<double> _iconTurns; + late Animation<double> _heightFactor; + late Animation<Color?> _borderColor; + late Animation<Color?> _headerColor; + late Animation<Color?> _iconColor; + late Animation<Color?> _backgroundColor; + + bool _isExpanded = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController(duration: _kExpand, vsync: this); + _heightFactor = _controller.drive(_easeInTween); + _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); + _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween)); + _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); + _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); + _backgroundColor = + _controller.drive(_backgroundColorTween.chain(_easeOutTween)); + + _isExpanded = PageStorage.of(context)?.readState(context) as bool? ?? + widget.initiallyExpanded; + if (_isExpanded) _controller.value = 1.0; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _handleTap() { + setState(() { + _isExpanded = !_isExpanded; + if (_isExpanded) { + _controller.forward(); + } else { + _controller.reverse().then<void>((void value) { + if (!mounted) return; + setState(() { + // Rebuild without widget.children. + }); + }); + } + PageStorage.of(context)?.writeState(context, _isExpanded); + }); + widget.onExpansionChanged?.call(_isExpanded); + } + + // Platform or null affinity defaults to trailing. + ListTileControlAffinity _effectiveAffinity( + ListTileControlAffinity? affinity) { + switch (affinity ?? ListTileControlAffinity.trailing) { + case ListTileControlAffinity.leading: + return ListTileControlAffinity.leading; + case ListTileControlAffinity.trailing: + case ListTileControlAffinity.platform: + return ListTileControlAffinity.trailing; + } + } + + Widget? _buildIcon(BuildContext context) { + return RotationTransition( + turns: _iconTurns, + child: const Icon(Icons.add), + ); + } + + Widget? _buildLeadingIcon(BuildContext context) { + if (_effectiveAffinity(widget.controlAffinity) != + ListTileControlAffinity.leading) return null; + return _buildIcon(context); + } + + Widget? _buildTrailingIcon(BuildContext context) { + if (_effectiveAffinity(widget.controlAffinity) != + ListTileControlAffinity.trailing) return null; + return _buildIcon(context); + } + + Widget _buildChildren(BuildContext context, Widget? child) { + final Color borderSideColor = _borderColor.value ?? Colors.transparent; + + return Container( + decoration: BoxDecoration( + color: _backgroundColor.value ?? Colors.transparent, + border: Border( + top: BorderSide(color: borderSideColor), + bottom: BorderSide(color: borderSideColor), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: <Widget>[ + ListTileTheme.merge( + iconColor: _iconColor.value, + textColor: _headerColor.value, + child: ListTile( + onTap: _handleTap, + contentPadding: widget.tilePadding, + leading: widget.leading ?? _buildLeadingIcon(context), + title: widget.title, + subtitle: widget.subtitle, + trailing: _isExpanded ? Icon(Icons.remove) : Icon(Icons.add), + ), + ), + ClipRect( + child: Align( + alignment: widget.expandedAlignment ?? Alignment.center, + heightFactor: _heightFactor.value, + child: child, + ), + ), + ], + ), + ); + } + + @override + void didChangeDependencies() { + final ThemeData theme = Theme.of(context); + final ColorScheme colorScheme = theme.colorScheme; + _borderColorTween.end = theme.dividerColor; + _headerColorTween + ..begin = widget.collapsedTextColor ?? theme.textTheme.subtitle1!.color + ..end = widget.textColor ?? colorScheme.primary; + _iconColorTween + ..begin = widget.collapsedIconColor ?? theme.unselectedWidgetColor + ..end = widget.iconColor ?? colorScheme.primary; + _backgroundColorTween + ..begin = widget.collapsedBackgroundColor + ..end = widget.backgroundColor; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final bool closed = !_isExpanded && _controller.isDismissed; + final bool shouldRemoveChildren = closed && !widget.maintainState; + + final Widget result = Offstage( + offstage: closed, + child: TickerMode( + enabled: !closed, + child: Padding( + padding: widget.childrenPadding ?? EdgeInsets.zero, + child: Column( + crossAxisAlignment: + widget.expandedCrossAxisAlignment ?? CrossAxisAlignment.center, + children: widget.children, + ), + ), + ), + ); + + return AnimatedBuilder( + animation: _controller.view, + builder: _buildChildren, + child: shouldRemoveChildren ? null : result, + ); + } +} diff --git a/lib/widgets/custom_navigator.dart b/lib/widgets/custom_navigator.dart new file mode 100644 index 0000000..0fb7ee9 --- /dev/null +++ b/lib/widgets/custom_navigator.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class CustomNavigator extends PageRouteBuilder { + CustomNavigator({required this.child}) + : super( + transitionDuration: Duration(milliseconds: 100), + pageBuilder: (context, animation, secondaryAnimation) => child, + ); + + final Widget child; + + @override + Widget buildTransitions(BuildContext context, Animation<double> animation, + Animation<double> secondaryAnimation, Widget child) => + SlideTransition( + position: Tween<Offset>( + begin: Offset(1, 0), + end: Offset.zero, + ).animate(animation), + child: child, + ); +} diff --git a/lib/widgets/custom_navigator_bottom.dart b/lib/widgets/custom_navigator_bottom.dart new file mode 100644 index 0000000..45072bb --- /dev/null +++ b/lib/widgets/custom_navigator_bottom.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class CustomNavigatorBottom extends PageRouteBuilder { + CustomNavigatorBottom({required this.child}) + : super( + transitionDuration: Duration(milliseconds: 100), + pageBuilder: (context, animation, secondaryAnimation) => child, + ); + + final Widget child; + + @override + Widget buildTransitions(BuildContext context, Animation<double> animation, + Animation<double> secondaryAnimation, Widget child) => + SlideTransition( + position: Tween<Offset>( + begin: Offset(0, 1), + end: Offset.zero, + ).animate(animation), + child: child, + ); +} diff --git a/lib/widgets/custom_navigator_pop.dart b/lib/widgets/custom_navigator_pop.dart new file mode 100644 index 0000000..89257a0 --- /dev/null +++ b/lib/widgets/custom_navigator_pop.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class CustomNavigatorPop extends PageRouteBuilder { + CustomNavigatorPop({required this.child}) + : super( + transitionDuration: Duration(milliseconds: 100), + pageBuilder: (context, animation, secondaryAnimation) => child, + ); + + final Widget child; + + @override + Widget buildTransitions(BuildContext context, Animation<double> animation, + Animation<double> secondaryAnimation, Widget child) => + SlideTransition( + position: Tween<Offset>( + begin: Offset(-1, 0), + end: Offset.zero, + ).animate(animation), + child: child, + ); +} diff --git a/lib/widgets/edit_qna_user.dart b/lib/widgets/edit_qna_user.dart new file mode 100644 index 0000000..07baf6e --- /dev/null +++ b/lib/widgets/edit_qna_user.dart @@ -0,0 +1,282 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/main.dart'; +import 'package:initial_folder/providers/posting_qna_provider.dart'; +import 'package:initial_folder/providers/qna_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:quill_html_editor/quill_html_editor.dart'; + +import '../size_config.dart'; +import '../theme.dart'; + +class EditQna extends StatefulWidget { + const EditQna( + {Key? key, + required this.quest, + required this.title, + required this.id_lesson, + required this.id_qna, + required this.id_course,}) + : super(key: key); + + final quest; + final id_lesson; + final title; + final id_qna; + final id_course; + + @override + State<EditQna> createState() => _EditQnaState(); +} + +class _EditQnaState extends State<EditQna> { + final TextEditingController _controllerTitle = TextEditingController(); + final QuillEditorController _controllerQuest = QuillEditorController(); + + final customToolbar = [ + ToolBarStyle.headerOne, + ToolBarStyle.headerTwo, + ToolBarStyle.bold, + ToolBarStyle.italic, + ToolBarStyle.underline, + ToolBarStyle.color, + ToolBarStyle.listBullet, + ToolBarStyle.listOrdered, + ]; + + late String initialTitle; + late String initialQuest; + + double value = 0; + + @override + void initState() { + initialTitle = widget.title; + initialQuest = widget.quest; + + _controllerTitle.text = widget.title; + super.initState(); + } + + @override + void dispose() { + _controllerTitle.dispose(); + _controllerQuest.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + PostingQnaProvider editQnaProvider = + Provider.of<PostingQnaProvider>(context); + return Scaffold( + appBar: AppBar( + title: Text( + 'Edit Pertanyaan', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16), + letterSpacing: 0.2, + ), + ), + ), + body: Stack( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Column( + children: [ + SizedBox( + height: 15, + ), + TextField( + controller: _controllerTitle, + cursorColor: secondaryColor, + scrollPadding: EdgeInsets.zero, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + borderSide: BorderSide.none, + ), + hintStyle: secondaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12), + ), + hintText: "Masukkan Judul Pertanyaan", + ), + ), + SizedBox(height: getProportionateScreenWidth(18)), + + ToolBar( + toolBarColor: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200]!, + activeIconColor: primaryColor, + iconColor: secondaryColor, + padding: const EdgeInsets.all(8), + iconSize: 20, + controller: _controllerQuest, + toolBarConfig: customToolbar, + ), + + Container( + height: 180, + width: double.infinity, + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: Colors.grey, + blurRadius: 0.5, + offset: Offset(0, 2), + spreadRadius: 0.001, + ), + ], + ), + child: QuillHtmlEditor( + hintText: 'Edit pertanyaan Anda', + controller: _controllerQuest, + isEnabled: true, + minHeight: 100, + backgroundColor: + Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200]!, + textStyle: secondaryTextStyle.copyWith( + // color: secondaryColor, + fontSize: getProportionateScreenWidth(16), + ), + hintTextStyle: secondaryTextStyle.copyWith( + // color: secondaryColor, + fontSize: getProportionateScreenWidth(16), + ), + hintTextAlign: TextAlign.start, + padding: const EdgeInsets.all(3), + hintTextPadding: const EdgeInsets.all(0), + loadingBuilder: (context) { + return const Center( + child: CircularProgressIndicator( + strokeWidth: 0.4, + ), + ); + }, + onEditorCreated: () { + if (widget.quest.isNotEmpty) { + _controllerQuest.setText(widget.quest); + } + }, + ), + ), + SizedBox(height: getProportionateScreenHeight(4)), + + Align( + alignment: Alignment.topRight, + child: ElevatedButton( + onPressed: () async { + String currentTitle = _controllerTitle.text; + String currentQuest = await _controllerQuest.getText(); + + String currentQuestTrimmed = currentQuest.trim(); + String initialQuestTrimmed = initialQuest.trim(); + + // Cek apakah ada perubahan + if (currentTitle == initialTitle && + currentQuestTrimmed == initialQuestTrimmed) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Tidak ada perubahan', + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } else { + bool success = await editQnaProvider.editQna( + widget.id_course, + currentQuest, + int.parse(widget.id_qna.toString()), + currentTitle, + widget.id_lesson, + ); + if (success) { + ScaffoldMessenger.of(globalScaffoldKey.currentContext!) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Pertanyaan berhasil diedit', + style: primaryTextStyle.copyWith( + color:Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + _controllerQuest.clear(); + _controllerTitle.clear(); + Navigator.pop(context); + } else { + ScaffoldMessenger.of(globalScaffoldKey.currentContext!) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Terjadi kesalahan', + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } + } + }, + child: Text( + 'Edit Pertanyaan', + style: thirdTextStyle.copyWith( + color: Colors.white, + fontSize: SizeConfig.blockHorizontal! * 4, + ), + + ), + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + ), + ), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/widgets/edit_reply_qna_user.dart b/lib/widgets/edit_reply_qna_user.dart new file mode 100644 index 0000000..8ea0f01 --- /dev/null +++ b/lib/widgets/edit_reply_qna_user.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/posting_qna_reply_provider.dart'; +import 'package:initial_folder/screens/course/component/detail_quest_and_answer.dart'; +import 'package:provider/provider.dart'; + +import '../size_config.dart'; +import '../theme.dart'; + +class EditReplyQna extends StatefulWidget { + const EditReplyQna( + {Key? key, + required this.id_qna, + required this.text_rep, + required this.id_rep}) + : super(key: key); + + final id_qna; + final text_rep; + final id_rep; + + @override + State<EditReplyQna> createState() => _EditReplyQnaState(); +} + +class _EditReplyQnaState extends State<EditReplyQna> { + final _textControlBalasan = TextEditingController(); + double value = 0; + + @override + void initState() { + if (widget.text_rep != null) { + _textControlBalasan.text = widget.text_rep ?? ''; + } + super.initState(); + } + + @override + void dispose() { + _textControlBalasan.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + PostingQnaReplyProvider editQnaReplyProvider = + Provider.of<PostingQnaReplyProvider>(context); + return Scaffold( + appBar: AppBar( + title: Text( + 'Edit Balasan', + style: primaryTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(16), + letterSpacing: 0.2, + ), + ), + ), + body: Stack( + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Column( + children: [ + SizedBox( + height: 15, + ), + TextField( + controller: _textControlBalasan, + cursorColor: secondaryColor, + scrollPadding: EdgeInsets.zero, + minLines: 2, + keyboardType: TextInputType.multiline, + maxLines: null, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : Colors.grey[200], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), + borderSide: BorderSide.none), + hintStyle: secondaryTextStyle.copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontSize: getProportionateScreenWidth(12), + ), + ), + ), + SizedBox( + height: getProportionateScreenHeight(4), + ), + Align( + alignment: Alignment.topRight, + child: ElevatedButton( + onPressed: () async { + if (await editQnaReplyProvider + .editQnaReply( + _textControlBalasan.text, + int.parse(widget.id_rep.toString()), + widget.id_qna) + .whenComplete( + () { + _textControlBalasan.clear(); + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Balasan berhasil diedit', + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + return Navigator.pop(context, true); + // await Provider.of(context)<QnaProvider>(context, + // listen: true) + // .getQna(widget.id_course); + }, + )) + ; + else { + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Terjadi kesalahan', + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } + }, + child: Text( + 'Edit Balasan', + style: thirdTextStyle.copyWith( + color: Colors.white, + fontSize: SizeConfig.blockHorizontal! * 4, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + ), + ), + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/widgets/list_notifikasi.dart b/lib/widgets/list_notifikasi.dart new file mode 100644 index 0000000..2bafcda --- /dev/null +++ b/lib/widgets/list_notifikasi.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; +class ListNotifikasi extends StatelessWidget { + const ListNotifikasi({Key? key, required this.data}) : super(key: key); + final List data; + + @override + Widget build(BuildContext context) { + return Text(data[0].toString()); + } +} diff --git a/lib/widgets/loading/loading_my_course.dart b/lib/widgets/loading/loading_my_course.dart new file mode 100644 index 0000000..c09a001 --- /dev/null +++ b/lib/widgets/loading/loading_my_course.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class LoadingMyCourse extends StatelessWidget { + const LoadingMyCourse({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + top: getProportionateScreenWidth(10), + ), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 11, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: getProportionateScreenWidth(156), + height: getProportionateScreenWidth(88), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: secondaryColor), + ), + SizedBox( + height: getProportionateScreenHeight(8), + ), + ], + )), + SizedBox( + width: getProportionateScreenWidth(9), + ), + Flexible( + flex: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: secondaryColor), + ), + SizedBox( + height: getProportionateScreenWidth(8), + ), + Container( + height: 8, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: secondaryColor), + ), + SizedBox( + height: getProportionateScreenWidth(6), + ), + Row( + children: [ + Container( + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: secondaryColor), + ), + SizedBox( + width: getProportionateScreenWidth(5), + ), + Container( + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: secondaryColor), + ), + ], + ), + ], + ), + ), + ], + ), + Container( + height: getProportionateScreenWidth(4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: secondaryColor), + ), + SizedBox(height: getProportionateScreenWidth(6)), + Align( + alignment: Alignment.topLeft, + child: Container( + width: 30, + height: 10, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: secondaryColor), + ), + ), + Row(children: [ + Container( + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: secondaryColor), + ), + SizedBox( + width: getProportionateScreenWidth(10), + ), + Container( + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: secondaryColor), + ), + ]), + ], + ), + ), + Divider( + color: fourthColor, + ), + ], + ); + } +} diff --git a/lib/widgets/login_regist/custom_font_awesome.dart b/lib/widgets/login_regist/custom_font_awesome.dart new file mode 100644 index 0000000..694e69b --- /dev/null +++ b/lib/widgets/login_regist/custom_font_awesome.dart @@ -0,0 +1,978 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +IconData fontAwesomeIconsFromString(String name) { + switch (name) { + case "FontAwesomeIcons.moneyBillAlt": + return FontAwesomeIcons.moneyBillAlt; + + case "FontAwesomeIcons.desktop": + return FontAwesomeIcons.desktop; + + case "FontAwesomeIcons.music": + return FontAwesomeIcons.music; + + case "FontAwesomeIcons.pencilAlt": + return FontAwesomeIcons.pencilAlt; + + case "FontAwesomeIcons.pencilRuler": + return FontAwesomeIcons.pencilRuler; + + case "FontAwesomeIcons.shoppingBag": + return FontAwesomeIcons.shoppingBag; + + case "FontAwesomeIcons.chartLine": + return FontAwesomeIcons.chartLine; + + case "FontAwesomeIcons.clipboardList": + return FontAwesomeIcons.clipboardList; + + case "FontAwesomeIcons.chess": + return FontAwesomeIcons.chess; + + case "FontAwesomeIcons.cameraRetro": + return FontAwesomeIcons.cameraRetro; + + case "FontAwesomeIcons.graduationCap": + return FontAwesomeIcons.graduationCap; + + case "FontAwesomeIcons.accessibleIcon": + return FontAwesomeIcons.accessibleIcon; + + case "FontawesomeIcons.accusoft": + return FontAwesomeIcons.accusoft; + + case "FontAwesomeIcons.acquisitionIncorporated": + return FontAwesomeIcons.a; + + case "FontAwesomeIcons.ad": + return FontAwesomeIcons.ad; + + case "FontAwesomeIcons.addressBook": + return FontAwesomeIcons.addressBook; + + case "FontAwesomeIcons.addressCard": + return FontAwesomeIcons.addressCard; + + case "FontAwesomeIcons.adjust": + return FontAwesomeIcons.adjust; + + case "FontAwesomeIcons.adn": + return FontAwesomeIcons.adn; + + case "FontAwesomeIcons.adversal": + return FontAwesomeIcons.adversal; + + case "FontAwesomeIcons.affiliatetheme": + return FontAwesomeIcons.affiliatetheme; + + case "FontAwesomeIcons.airFreshener": + return FontAwesomeIcons.airFreshener; + + case "FontAwesomeIcons.airbnb": + return FontAwesomeIcons.airbnb; + + case "FontAwesomeIcons.algolia": + return FontAwesomeIcons.algolia; + + case "FontAwesomeIcons.alignCenter": + return FontAwesomeIcons.alignCenter; + + case "FontAwesomeIcons.alignJustify": + return FontAwesomeIcons.alignJustify; + + case "FontAwesomeIcons.alignLeft": + return FontAwesomeIcons.alignLeft; + + case "FontAwesomeIcons.alignRight": + return FontAwesomeIcons.alignRight; + + case "FontAwesomeIcons.alipay": + return FontAwesomeIcons.alipay; + + case "FontAwesomeIcons.allergies": + return FontAwesomeIcons.allergies; + + case "FontAwesomeIcons.amazon": + return FontAwesomeIcons.amazon; + + case "FontAwesomeIcons.amazonPay": + return FontAwesomeIcons.amazonPay; + + case "FontAwesomeIcons.ambulance": + return FontAwesomeIcons.ambulance; + + case "FontAwesomeIcons.americanSignLanguageInterpreting": + return FontAwesomeIcons.americanSignLanguageInterpreting; + + case "FontAwesomeIcons.amilia": + return FontAwesomeIcons.amilia; + + case "FontAwesomeIcons.anchor": + return FontAwesomeIcons.anchor; + + case "FontAwesomeIcons.android": + return FontAwesomeIcons.android; + + case "FontAwesomeIcons.angellist": + return FontAwesomeIcons.angellist; + + case "FontAwesomeIcons.angleDoubleDown": + return FontAwesomeIcons.angleDoubleDown; + + case "FontAwesomeIcons.angleDoubleLeft": + return FontAwesomeIcons.angleDoubleLeft; + + case "FontAwesomeIcons.angleDoubleRight": + return FontAwesomeIcons.angleDoubleRight; + + case "FontAwesomeIcons.angleDoubleUp": + return FontAwesomeIcons.angleDoubleUp; + + case "FontAwesomeIcons.angleDown": + return FontAwesomeIcons.angleDown; + + case "FontAwesomeIcons.angleLeft": + return FontAwesomeIcons.angleLeft; + + case "FontAwesomeIcons.angleRight": + return FontAwesomeIcons.angleRight; + + case "FontAwesomeIcons.angleUp": + return FontAwesomeIcons.angleUp; + + case "FontAwesomeIcons.angry": + return FontAwesomeIcons.angry; + + case "FontAwesomeIcons.angrycreative": + return FontAwesomeIcons.angrycreative; + + case "FontAwesomeIcons.angular": + return FontAwesomeIcons.angular; + + case "FontAwesomeIcons.ankh": + return FontAwesomeIcons.ankh; + + case "FontAwesomeIcons.appStore": + return FontAwesomeIcons.appStore; + + case "FontAwesomeIcons.appStoreIos": + return FontAwesomeIcons.appStoreIos; + + case "FontAwesomeIcons.apper": + return FontAwesomeIcons.apper; + + case "FontAwesomeIcons.apple": + return FontAwesomeIcons.apple; + + case "FontAwesomeIcons.appleAlt": + return FontAwesomeIcons.appleAlt; + + case "FontAwesomeIcons.applePay": + return FontAwesomeIcons.applePay; + + case "FontAwesomeIcons.archive": + return FontAwesomeIcons.archive; + + case "FontAwesomeIcons.archway": + return FontAwesomeIcons.archway; + + case "FontAwesomeIcons.arrowAltCircleDown": + return FontAwesomeIcons.arrowAltCircleDown; + + case "FontAwesomeIcons.arrowAltCircleLeft": + return FontAwesomeIcons.arrowAltCircleLeft; + + case "FontAwesomeIcons.arrowAltCircleRight": + return FontAwesomeIcons.arrowAltCircleRight; + + case "FontAwesomeIcons.arrowAltCircleUp": + return FontAwesomeIcons.arrowAltCircleUp; + + case "FontAwesomeIcons.arrowCircleDown": + return FontAwesomeIcons.arrowCircleDown; + + case "FontAwesomeIcons.arrowCircleLeft": + return FontAwesomeIcons.arrowCircleLeft; + + case "FontAwesomeIcons.arrowCircleRight": + return FontAwesomeIcons.arrowCircleRight; + + case "FontAwesomeIcons.arrowCircleUp": + return FontAwesomeIcons.arrowCircleUp; + + case "FontAwesomeIcons.arrowDown": + return FontAwesomeIcons.arrowDown; + + case "FontAwesomeIcons.arrowLeft": + return FontAwesomeIcons.arrowLeft; + + case "FontAwesomeIcons.arrowRight": + return FontAwesomeIcons.arrowRight; + + case "FontAwesomeIcons.arrowUp": + return FontAwesomeIcons.arrowUp; + + case "FontAwesomeIcons.arrowsAlt": + return FontAwesomeIcons.arrowsAlt; + + case "FontAwesomeIcons.arrowsAltH": + return FontAwesomeIcons.arrowsAltH; + + case "FontAwesomeIcons.arrowsAltV": + return FontAwesomeIcons.arrowsAltV; + + case "FontAwesomeIcons.artstation": + return FontAwesomeIcons.artstation; + + case "FontAwesomeIcons.assistiveListeningSystems": + return FontAwesomeIcons.assistiveListeningSystems; + + case "FontAwesomeIcons.asterisk": + return FontAwesomeIcons.asterisk; + + case "FontAwesomeIcons.asymmetrik": + return FontAwesomeIcons.asymmetrik; + + case "FontAwesomeIcons.at": + return FontAwesomeIcons.at; + + case "FontAwesomeIcons.atlas": + return FontAwesomeIcons.atlas; + + case "FontAwesomeIcons.atlassian": + return FontAwesomeIcons.atlassian; + + case "FontAwesomeIcons.atom": + return FontAwesomeIcons.atom; + + case "FontAwesomeIcons.audible": + return FontAwesomeIcons.audible; + + case "FontAwesomeIcons.audioDescription": + return FontAwesomeIcons.audioDescription; + + case "FontAwesomeIcons.autoprefixer": + return FontAwesomeIcons.autoprefixer; + + case "FontAwesomeIcons.avianex": + return FontAwesomeIcons.avianex; + + case "FontAwesomeIcons.aviato": + return FontAwesomeIcons.aviato; + + case "FontAwesomeIcons.award": + return FontAwesomeIcons.award; + + case "FontAwesomeIcons.aws": + return FontAwesomeIcons.aws; + + case "FontAwesomeIcons.baby": + return FontAwesomeIcons.baby; + + case "FontAwesomeIcons.babyCarriage": + return FontAwesomeIcons.babyCarriage; + + case "FontAwesomeIcons.backspace": + return FontAwesomeIcons.backspace; + + case "FontAwesomeIcons.backward": + return FontAwesomeIcons.backward; + + case "FontAwesomeIcons.bacon": + return FontAwesomeIcons.bacon; + + case "FontAwesomeIcons.bahai": + return FontAwesomeIcons.bahai; + + case "FontAwesomeIcons.balanceScale": + return FontAwesomeIcons.balanceScale; + + case "FontAwesomeIcons.balanceScaleLeft": + return FontAwesomeIcons.balanceScaleLeft; + + case "FontAwesomeIcons.balanceScaleRight": + return FontAwesomeIcons.balanceScaleRight; + + case "FontAwesomeIcons.ban": + return FontAwesomeIcons.ban; + + case "FontAwesomeIcons.bandAid": + return FontAwesomeIcons.bandAid; + + case "FontAwesomeIcons.bandcamp": + return FontAwesomeIcons.bandcamp; + + case "FontAwesomeIcons.barcode": + return FontAwesomeIcons.barcode; + + case "FontAwesomeIcons.bars": + return FontAwesomeIcons.bars; + + case "FontAwesomeIcons.baseballBall": + return FontAwesomeIcons.baseballBall; + + case "FontAwesomeIcons.basketballBall": + return FontAwesomeIcons.basketballBall; + + case "FontAwesomeIcons.bath": + return FontAwesomeIcons.bath; + + case "FontAwesomeIcons.batteryEmpty": + return FontAwesomeIcons.batteryEmpty; + + case "FontAwesomeIcons.batteryFull": + return FontAwesomeIcons.batteryFull; + + case "FontAwesomeIcons.batteryHalf": + return FontAwesomeIcons.batteryHalf; + + case "FontAwesomeIcons.batteryQuarter": + return FontAwesomeIcons.batteryQuarter; + + case "FontAwesomeIcons.batteryThreeQuarters": + return FontAwesomeIcons.batteryThreeQuarters; + + case "FontAwesomeIcons.battleNet": + return FontAwesomeIcons.battleNet; + + case "FontAwesomeIcons.bed": + return FontAwesomeIcons.bed; + + case 'FontAwesomeIcons.beer': + return FontAwesomeIcons.beer; + + case "FontAwesomeIcons.behance": + return FontAwesomeIcons.behance; + + case "FontAwesomeIcons.behanceSquare": + return FontAwesomeIcons.behanceSquare; + + case "FontAwesomeIcons.bell": + return FontAwesomeIcons.bell; + + case "FontAwesomeIcons.bellSlash": + return FontAwesomeIcons.bellSlash; + + case "FontAwesomeIcons.bezierCurve": + return FontAwesomeIcons.bezierCurve; + + case "FontAwesomeIcons.bible": + return FontAwesomeIcons.bible; + + case "FontAwesomeIcons.bicycle": + return FontAwesomeIcons.bicycle; + + case "FontAwesomeIcons.biking": + return FontAwesomeIcons.biking; + + case "FontAwesomeIcons.bimobject": + return FontAwesomeIcons.bimobject; + + case "FontAwesomeIcons.binoculars": + return FontAwesomeIcons.binoculars; + + case "FontAwesomeIcons.biohazard": + return FontAwesomeIcons.biohazard; + + case "FontAwesomeIcons.birthdayCake": + return FontAwesomeIcons.birthdayCake; + + case "FontAwesomeIcons.bitbucket": + return FontAwesomeIcons.bitbucket; + + case "FontAwesomeIcons.bitcoin": + return FontAwesomeIcons.bitcoin; + + case "FontAwesomeIcons.bity": + return FontAwesomeIcons.bity; + + case "FontAwesomeIcons.blackTie": + return FontAwesomeIcons.blackTie; + + case "FontAwesomeIcons.blackberry": + return FontAwesomeIcons.blackberry; + + case "FontAwesomeIcons.blender": + return FontAwesomeIcons.blender; + + case "FontAwesomeIcons.blenderPhone": + return FontAwesomeIcons.blenderPhone; + + case "FontAwesomeIcons.blind": + return FontAwesomeIcons.blind; + + case "FontAwesomeIcons.blog": + return FontAwesomeIcons.blog; + + case "FontAwesomeIcons.blogger": + return FontAwesomeIcons.blogger; + + case "FontAwesomeIcons.bloggerB": + return FontAwesomeIcons.bloggerB; + + case "FontAwesomeIcons.bluetooth": + return FontAwesomeIcons.bluetooth; + + case "FontAwesomeIcons.bluetoothB": + return FontAwesomeIcons.bluetoothB; + + case "FontAwesomeIcons.bold": + return FontAwesomeIcons.bold; + + case "FontAwesomeIcons.bolt": + return FontAwesomeIcons.bolt; + + case "FontAwesomeIcons.bomb": + return FontAwesomeIcons.bomb; + + case "FontAwesomeIcons.bone": + return FontAwesomeIcons.bone; + + case "FontAwesomeIcons.bong": + return FontAwesomeIcons.bong; + + case "FontAwesomeIcons.book": + return FontAwesomeIcons.book; + + case "FontAwesomeIcons.bookDead": + return FontAwesomeIcons.bookDead; + + case "FontAwesomeIcons.bookMedical": + return FontAwesomeIcons.bookMedical; + + case "FontAwesomeIcons.bookOpen": + return FontAwesomeIcons.bookOpen; + + case "FontAwesomeIcons.bookReader": + return FontAwesomeIcons.bookReader; + + case "FontAwesomeIcons.bookmark": + return FontAwesomeIcons.bookmark; + + case "FontAwesomeIcons.bootstrap": + return FontAwesomeIcons.bootstrap; + + case "FontAwesomeIcons.borderAll": + return FontAwesomeIcons.borderAll; + + case "FontAwesomeIcons.borderNone": + return FontAwesomeIcons.borderNone; + + case "FontAwesomeIcons.borderStyle": + return FontAwesomeIcons.borderStyle; + + case "FontAwesomeIcons.bowlingBall": + return FontAwesomeIcons.bowlingBall; + + case "FontAwesomeIcons.box": + return FontAwesomeIcons.box; + + case "FontAwesomeIcons.boxOpen": + return FontAwesomeIcons.boxOpen; + + case "FontAwesomeIcons.boxTissue": + return FontAwesomeIcons.boxTissue; + + case "FontAwesomeIcons.boxes": + return FontAwesomeIcons.boxes; + + case "FontAwesomeIcons.braille": + return FontAwesomeIcons.braille; + + case "FontAwesomeIcons.brain": + return FontAwesomeIcons.brain; + + case "FontAwesomeIcons.breadSlice": + return FontAwesomeIcons.breadSlice; + + case "FontAwesomeIcons.briefcase": + return FontAwesomeIcons.briefcase; + + case "FontAwesomeIcons.briefcaseMedical": + return FontAwesomeIcons.briefcaseMedical; + + case "FontAwesomeIcons.broadcastTower": + return FontAwesomeIcons.broadcastTower; + + case "FontAwesomeIcons.broom": + return FontAwesomeIcons.broom; + + case "FontAwesomeIcons.brush": + return FontAwesomeIcons.brush; + + case "FontAwesomeIcons.btc": + return FontAwesomeIcons.btc; + + case "FontAwesomeIcons.buffer": + return FontAwesomeIcons.buffer; + + case "FontAwesomeIcons.bug": + return FontAwesomeIcons.bug; + + case "FontAwesomeIcons.building": + return FontAwesomeIcons.building; + + case "FontAwesomeIcons.bullhorn": + return FontAwesomeIcons.bullhorn; + + case "FontAwesomeIcons.bullseye": + return FontAwesomeIcons.bullseye; + + case "FontAwesomeIcons.burn": + return FontAwesomeIcons.burn; + + case "FontAwesomeIcons.buromobelexperte": + return FontAwesomeIcons.buromobelexperte; + + case "FontAwesomeIcons.bus": + return FontAwesomeIcons.bus; + + case "FontAwesomeIcons.busAlt": + return FontAwesomeIcons.busAlt; + + case "FontAwesomeIcons.businessTime": + return FontAwesomeIcons.businessTime; + + case "FontAwesomeIcons.buyNLarge": + return FontAwesomeIcons.buyNLarge; + + case "FontAwesomeIcons.buysellads": + return FontAwesomeIcons.buysellads; + + case "FontAwesomeIcons.calculator": + return FontAwesomeIcons.calculator; + + case "FontAwesomeIcons.calendar": + return FontAwesomeIcons.calendar; + + case "FontAwesomeIcons.calendarAlt": + return FontAwesomeIcons.calendarAlt; + + case "FontAwesomeIcons.calendarCheck": + return FontAwesomeIcons.calendarCheck; + + case "FontAwesomeIcons.calendarDay": + return FontAwesomeIcons.calendarDay; + + case "FontAwesomeIcons.calendarMinus": + return FontAwesomeIcons.calendarMinus; + + case "FontAwesomeIcons.calendarPlus": + return FontAwesomeIcons.calendarPlus; + + case "FontAwesomeIcons.calendarTimes": + return FontAwesomeIcons.calendarTimes; + + case "FontAwesomeIcons.calendarWeek": + return FontAwesomeIcons.calendarWeek; + + case "FontAwesomeIcons.camera": + return FontAwesomeIcons.camera; + + case "FontAwesomeIcons.campground": + return FontAwesomeIcons.campground; + + case "FontAwesomeIcons.canadianMapleLeaf": + return FontAwesomeIcons.canadianMapleLeaf; + + case "FontAwesomeIcons.candyCane": + return FontAwesomeIcons.candyCane; + + case "FontAwesomeIcons.cannabis": + return FontAwesomeIcons.cannabis; + + case "FontAwesomeIcons.capsules": + return FontAwesomeIcons.capsules; + + case "FontAwesomeIcons.car": + return FontAwesomeIcons.car; + + case "FontAwesomeIcons.carAlt": + return FontAwesomeIcons.carAlt; + + case "FontAwesomeIcons.carBattery": + return FontAwesomeIcons.carBattery; + + case "FontAwesomeIcons.carCrash": + return FontAwesomeIcons.carCrash; + + case "FontAwesomeIcons.carSide": + return FontAwesomeIcons.carSide; + + case "FontAwesomeIcons.caravan": + return FontAwesomeIcons.caravan; + + case "FontAwesomeIcons.caretDown": + return FontAwesomeIcons.caretDown; + + case "FontAwesomeIcons.caretLeft": + return FontAwesomeIcons.caretLeft; + + case "FontAwesomeIcons.caretRight": + return FontAwesomeIcons.caretRight; + + case "FontAwesomeIcons.caretSquareDown": + return FontAwesomeIcons.caretSquareDown; + + case "FontAwesomeIcons.caretSquareLeft": + return FontAwesomeIcons.caretSquareLeft; + + case "FontAwesomeIcons.caretSquareRight": + return FontAwesomeIcons.caretSquareRight; + + case "FontAwesomeIcons.caretSquareUp": + return FontAwesomeIcons.caretSquareUp; + + case "FontAwesomeIcons.caretUp": + return FontAwesomeIcons.caretUp; + + case "FontAwesomeIcons.carrot": + return FontAwesomeIcons.carrot; + + case "FontAwesomeIcons.cartArrowDown": + return FontAwesomeIcons.cartArrowDown; + + case "FontAwesomeIcons.cartPlus": + return FontAwesomeIcons.cartPlus; + + case "FontAwesomeIcons.cashRegister": + return FontAwesomeIcons.cashRegister; + + case "FontAwesomeIcons.cat": + return FontAwesomeIcons.cat; + + case "FontAwesomeIcons.ccAmazonPay": + return FontAwesomeIcons.ccAmazonPay; + + case "FontAwesomeIcons.ccAmex": + return FontAwesomeIcons.ccAmex; + + case "FontAwesomeIcons.ccApplePay": + return FontAwesomeIcons.ccApplePay; + + case "FontAwesomeIcons.ccDinersClub": + return FontAwesomeIcons.ccDinersClub; + + case "FontAwesomeIcons.ccDiscover": + return FontAwesomeIcons.ccDiscover; + + case "FontAwesomeIcons.ccJcb": + return FontAwesomeIcons.ccJcb; + + case "FontAwesomeIcons.ccMastercard": + return FontAwesomeIcons.ccMastercard; + + case "FontAwesomeIcons.ccPaypal": + return FontAwesomeIcons.ccPaypal; + + case "FontAwesomeIcons.ccStripe": + return FontAwesomeIcons.ccStripe; + + case "FontAwesomeIcons.ccVisa": + return FontAwesomeIcons.ccVisa; + + case "FontAwesomeIcons.centercode": + return FontAwesomeIcons.centercode; + + case "FontAwesomeIcons.centos": + return FontAwesomeIcons.centos; + + case "FontAwesomeIcons.certificate": + return FontAwesomeIcons.certificate; + + case "FontAwesomeIcons.chair": + return FontAwesomeIcons.chair; + + case "FontAwesomeIcons.chalkboard": + return FontAwesomeIcons.chalkboard; + + case "FontAwesomeIcons.chalkboardTeacher": + return FontAwesomeIcons.chalkboardTeacher; + + case "FontAwesomeIcons.chargingStation": + return FontAwesomeIcons.chargingStation; + + case "FontAwesomeIcons.chartArea": + return FontAwesomeIcons.chartArea; + + case "FontAwesomeIcons.chartBar": + return FontAwesomeIcons.chartBar; + + case "FontAwesomeIcons.chartLine": + return FontAwesomeIcons.chartLine; + + case "FontAwesomeIcons.chartPie": + return FontAwesomeIcons.chartPie; + + case "FontAwesomeIcons.check": + return FontAwesomeIcons.check; + + case "FontAwesomeIcons.checkCircle": + return FontAwesomeIcons.checkCircle; + + case "FontAwesomeIcons.checkDouble": + return FontAwesomeIcons.checkDouble; + + case "FontAwesomeIcons.checkSquare": + return FontAwesomeIcons.checkSquare; + + case "FontAwesomeIcons.cheese": + return FontAwesomeIcons.cheese; + + case "FontAwesomeIcons.chessBishop": + return FontAwesomeIcons.chessBishop; + + case "FontAwesomeIcons.chessBoard": + return FontAwesomeIcons.chessBoard; + + case "FontAwesomeIcons.chessKing": + return FontAwesomeIcons.chessKing; + + case "FontAwesomeIcons.chessKnight": + return FontAwesomeIcons.chessKnight; + + case "FontAwesomeIcons.chessPawn": + return FontAwesomeIcons.chessPawn; + + case "FontAwesomeIcons.chessQueen": + return FontAwesomeIcons.chessQueen; + + case "FontAwesomeIcons.chessRook": + return FontAwesomeIcons.chessRook; + + case "FontAwesomeIcons.chevronCircleDown": + return FontAwesomeIcons.chevronCircleDown; + + case "FontAwesomeIcons.chevronCircleLeft": + return FontAwesomeIcons.chevronCircleLeft; + + case "FontAwesomeIcons.chevronCircleRight": + return FontAwesomeIcons.chevronCircleRight; + + case "FontAwesomeIcons.chevronCircleUp": + return FontAwesomeIcons.chevronCircleUp; + + case "FontAwesomeIcons.chevronDown": + return FontAwesomeIcons.chevronDown; + + case "FontAwesomeIcons.chevronLeft": + return FontAwesomeIcons.chevronLeft; + + case "FontAwesomeIcons.chevronRight": + return FontAwesomeIcons.chevronRight; + + case "FontAwesomeIcons.chevronUp": + return FontAwesomeIcons.chevronUp; + + case "FontAwesomeIcons.child": + return FontAwesomeIcons.child; + + case "FontAwesomeIcons.chrome": + return FontAwesomeIcons.chrome; + + case "FontAwesomeIcons.chromecast": + return FontAwesomeIcons.chromecast; + + case "FontAwesomeIcons.church": + return FontAwesomeIcons.church; + + case "FontAwesomeIcons.circle": + return FontAwesomeIcons.circle; + + case "FontAwesomeIcons.circleNotch": + return FontAwesomeIcons.circleNotch; + + case "FontAwesomeIcons.city": + return FontAwesomeIcons.city; + + case "FontAwesomeIcons.clinicMedical": + return FontAwesomeIcons.clinicMedical; + + case "FontAwesomeIcons.clipboard": + return FontAwesomeIcons.clipboard; + + case "FontAwesomeIcons.clipboardCheck": + return FontAwesomeIcons.clipboardCheck; + + case "FontAwesomeIcons.clipboardList": + return FontAwesomeIcons.clipboardList; + + case "FontAwesomeIcons.clock": + return FontAwesomeIcons.clock; + + case "FontAwesomeIcons.clone": + return FontAwesomeIcons.clone; + + case "FontAwesomeIcons.closedCaptioning": + return FontAwesomeIcons.closedCaptioning; + + case "FontAwesomeIcons.cloud": + return FontAwesomeIcons.cloud; + + case "FontAwesomeIcons.cloudDownloadAlt": + return FontAwesomeIcons.cloudDownloadAlt; + + case "FontAwesomeIcons.cloudMeatball": + return FontAwesomeIcons.cloudMeatball; + + case "FontAwesomeIcons.cloudMoon": + return FontAwesomeIcons.cloudMoon; + + case "FontAwesomeIcons.cloudMoonRain": + return FontAwesomeIcons.cloudMoonRain; + + case "FontAwesomeIcons.cloudRains": + return FontAwesomeIcons.cloudRain; + + case "FontAwesomeIcons.cloudShowersHeavy": + return FontAwesomeIcons.cloudShowersHeavy; + + case "FontAwesomeIcons.cloudSun": + return FontAwesomeIcons.cloudSun; + + case "FontAwesomeIcons.cloudSunRain": + return FontAwesomeIcons.cloudSunRain; + + case "FontAwesomeIcons.cloudUploadAlt": + return FontAwesomeIcons.cloudUploadAlt; + + case "FontAwesomeIcons.cloudscale": + return FontAwesomeIcons.cloudscale; + + case "FontAwesomeIcons.cloudsmith": + return FontAwesomeIcons.cloudsmith; + + case "FontAwesomeIcons.cloudversify": + return FontAwesomeIcons.cloudversify; + + case "FontAwesomeIcons.cocktail": + return FontAwesomeIcons.cocktail; + + case "FontAwesomeIcons.code": + return FontAwesomeIcons.code; + + case "FontAwesomeIcons.codeBranch": + return FontAwesomeIcons.codeBranch; + + case "FontAwesomeIcons.codepen": + return FontAwesomeIcons.codepen; + + case "FontAwesomeIcons.codiepie": + return FontAwesomeIcons.codiepie; + + case "FontAwesomeIcons.coffee": + return FontAwesomeIcons.coffee; + + case "FontAwesomeIcons.cog": + return FontAwesomeIcons.cog; + + case "FontAwesomeIcons.cogs": + return FontAwesomeIcons.cogs; + + case "FontAwesomeIcons.coins": + return FontAwesomeIcons.coins; + + case "FontAwesomeIcons.columns": + return FontAwesomeIcons.columns; + + case "FontAwesomeIcons.comment": + return FontAwesomeIcons.comment; + + case "FontAwesomeIcons.commentAlt": + return FontAwesomeIcons.commentAlt; + + case "FontAwesomeIcons.commentDollar": + return FontAwesomeIcons.commentDollar; + + case "FontAwesomeIcons.commentDots": + return FontAwesomeIcons.commentDots; + + case "FontAwesomeIcons.commentMedical": + return FontAwesomeIcons.commentMedical; + + case "FontAwesomeIcons.commentSlash": + return FontAwesomeIcons.commentSlash; + + case "FontAwesomeIcons.comments": + return FontAwesomeIcons.comments; + + case "FontAwesomeIcons.commentsDollar": + return FontAwesomeIcons.commentsDollar; + + case "FontAwesomeIcons.compactDisc": + return FontAwesomeIcons.compactDisc; + + case "FontAwesomeIcons.compass": + return FontAwesomeIcons.compass; + + case "FontAwesomeIcons.compress": + return FontAwesomeIcons.compress; + + case "FontAwesomeIcons.compressAlt": + return FontAwesomeIcons.compressAlt; + + case "FontAwesomeIcons.compressArrowsAlt": + return FontAwesomeIcons.compressArrowsAlt; + + case "FontAwesomeIcons.conciergeBell": + return FontAwesomeIcons.conciergeBell; + + case "FontAwesomeIcons.confluence": + return FontAwesomeIcons.confluence; + + case "FontAwesomeIcons.connectdevelop": + return FontAwesomeIcons.connectdevelop; + + case "FontAwesomeIcons.contao": + return FontAwesomeIcons.contao; + + case "FontAwesomeIcons.cookie": + return FontAwesomeIcons.cookie; + + case "FontAwesomeIcons.cookieBite": + return FontAwesomeIcons.cookieBite; + + case "FontAwesomeIcons.copy": + return FontAwesomeIcons.copy; + + case "FontAwesomeIcons.copyright": + return FontAwesomeIcons.copyright; + + case "FontAwesomeIcons.cottonBureau": + return FontAwesomeIcons.cottonBureau; + + case "FontAwesomeIcons.couch": + return FontAwesomeIcons.couch; + + case "FontAwesomeIcons.cpanel": + return FontAwesomeIcons.cpanel; + + case "FontAwesomeIcons.creativeCommons": + return FontAwesomeIcons.creativeCommons; + + case "FontAwesomeIcons.creativeCommonsBy": + return FontAwesomeIcons.creativeCommonsBy; + + case "FontAwesomeIcons.creativeCommonsNc": + return FontAwesomeIcons.creativeCommonsNc; + + case "FontAwesomeIcons.creativeCommonsNcEu": + return FontAwesomeIcons.creativeCommonsNcEu; + + case "FontAwesomeIcons.creativeCommonsNcJp": + return FontAwesomeIcons.creativeCommonsNcJp; + + case "FontAwesomeIcons.creativeCommonsNd": + return FontAwesomeIcons.creativeCommonsNd; + + case "FontAwesomeIcons.creativeCommonsPd": + return FontAwesomeIcons.creativeCommonsPd; + + case "": + return FontAwesomeIcons.creativeCommonsPdAlt; + + default: + return FontAwesomeIcons.home; + } +} diff --git a/lib/widgets/login_regist/custom_profile_text_field.dart b/lib/widgets/login_regist/custom_profile_text_field.dart new file mode 100644 index 0000000..3fc888e --- /dev/null +++ b/lib/widgets/login_regist/custom_profile_text_field.dart @@ -0,0 +1,175 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class CustomProfileTextField extends StatefulWidget { + CustomProfileTextField({ + Key? key, + this.length = 999, + this.minLines = 1, + this.maxLines = 1, + this.height = 13, + this.noTitle = false, + this.prefix, + this.suffix, + this.color = secondaryColor, + this.borderColor = Colors.white, + this.keyboardType = TextInputType.text, + this.title = '', + this.obscuretext = false, + this.auto = false, + this.validate, + this.enable = true, + this.onChanged, + required this.pad, + required this.text, + required this.hinttext, + this.isErrorManual = false, + this.textErrorManual = '', + }) : super(key: key); + final int length; + final Color color; + final int minLines; + final int maxLines; + final bool noTitle; + final Color borderColor; + final TextInputType keyboardType; + final bool auto; + final String title; + final bool obscuretext; + final String text; + final String hinttext; + final double pad; + final double height; + + // variabel untuk menangani error dari frontend + final FormFieldValidator<String>? validate; + + final Widget? prefix; + final Widget? suffix; + final bool enable; + final Function(String)? onChanged; + + // variabel untuk menangani error dari backend + final bool isErrorManual; + final String textErrorManual; + + @override + State<CustomProfileTextField> createState() => _CustomProfileTextFieldState(); +} + +class _CustomProfileTextFieldState extends State<CustomProfileTextField> { + late final TextEditingController controller; + + @override + void initState() { + super.initState(); + + controller = TextEditingController(text: widget.text); + } + + @override + void dispose() { + controller.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + return Container( + margin: EdgeInsets.symmetric(horizontal: widget.pad), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + widget.noTitle == false + ? Text( + widget.title, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12), + ), + ) + : SizedBox(height: 0), + widget.noTitle == false + ? SizedBox( + height: getProportionateScreenHeight(4), + ) + : SizedBox(height: 0), + Theme( + data: ThemeData.dark().copyWith(errorColor: sevenColor), + child: TextFormField( + // initialValue: val, + enabled: widget.enable, + minLines: widget.minLines, + maxLines: widget.maxLines, + inputFormatters: [ + LengthLimitingTextInputFormatter(widget.length), + ], + keyboardType: widget.keyboardType, + autofocus: widget.auto, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: widget.validate, + obscureText: widget.obscuretext, + controller: controller, + onChanged: widget.onChanged, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + color: Theme.of(context).brightness == Brightness.dark + ? secondaryColor + : baruTexthitam, + ), + cursorColor: secondaryColor, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : secondaryColor.withOpacity(0.3), + errorStyle: primaryTextStyle, + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: sevenColor), + borderRadius: BorderRadius.circular(10)), + suffixIcon: widget.suffix, + prefixIcon: widget.prefix, + contentPadding: EdgeInsets.only( + left: getProportionateScreenWidth(15), + top: getProportionateScreenHeight(widget.height), + bottom: getProportionateScreenHeight(widget.height)), + hintText: widget.hinttext, + hintStyle: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).brightness == Brightness.dark + ? widget.color + : baruTexthitam, + letterSpacing: 0.5), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), + borderSide: BorderSide.none), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), + // borderSide: BorderSide( + // color: widget.borderColor, + // ), + ), + errorText: widget.isErrorManual ? widget.textErrorManual : null, + ), + ), + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/login_regist/custom_text_form_field.dart b/lib/widgets/login_regist/custom_text_form_field.dart new file mode 100644 index 0000000..6601000 --- /dev/null +++ b/lib/widgets/login_regist/custom_text_form_field.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class CustomTextField extends StatelessWidget { + CustomTextField({ + Key? key, + this.length = 999, + this.minLines = 1, + this.maxLines = 1, + this.height = 13, + this.noTitle = false, + this.prefix, + this.suffix, + this.color = secondaryColor, + this.borderColor = Colors.white, + this.keyboardType = TextInputType.text, + this.title = '', + this.obscuretext = false, + this.auto = false, + this.validate, + this.enable = true, + required this.pad, + required this.controler, + required this.hinttext, + this.textInputAction, + this.digitOnly, + }) : super(key: key); + final int length; + final Color color; + final bool? digitOnly; + final int minLines; + final int maxLines; + final bool noTitle; + final Color borderColor; + final TextInputType keyboardType; + final bool auto; + final String title; + final bool obscuretext; + final TextEditingController controler; + final String hinttext; + final double pad; + final double height; + final FormFieldValidator<String>? validate; + final Widget? prefix; + final Widget? suffix; + final TextInputAction? textInputAction; + final bool enable; + // final String val; + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + return Container( + margin: EdgeInsets.symmetric(horizontal: pad), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + noTitle == false + ? Text( + title, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(12), + // color: baruTexthitam, + letterSpacing: 0.5), + ) + : SizedBox(height: 0), + noTitle == false + ? SizedBox( + height: getProportionateScreenHeight(4), + ) + : SizedBox(height: 0), + Theme( + data: ThemeData.dark(), + child: TextFormField( + // initialValue: val, + onChanged: (value) {}, + enabled: enable, + minLines: minLines, + maxLines: maxLines, + inputFormatters: [ + (digitOnly != null) + ? (digitOnly!) + ? FilteringTextInputFormatter.digitsOnly + : LengthLimitingTextInputFormatter(length) + : LengthLimitingTextInputFormatter(length) + ], + keyboardType: keyboardType, + // autofocus: auto, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: validate, + + obscureText: obscuretext, + controller: controler, + style: primaryTextStyle.copyWith( + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : baruTexthitam, + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ), + cursorColor: secondaryColor, + decoration: InputDecoration( + filled: true, + fillColor: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : secondaryColor.withOpacity(0.3), + // errorStyle: primaryTextStyle, + // errorBorder: OutlineInputBorder( + // borderSide: BorderSide(color: sevenColor), + // borderRadius: BorderRadius.circular(10)), + errorStyle: thirdTextStyle.copyWith(color: Colors.red), + suffixIcon: suffix, + prefixIcon: prefix, + contentPadding: EdgeInsets.only( + left: getProportionateScreenWidth(15), + top: getProportionateScreenHeight(height), + bottom: getProportionateScreenHeight(height)), + hintText: hinttext, + hintStyle: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: color, + letterSpacing: 0.5), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), + borderSide: BorderSide.none), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular( + 10, + ), + borderSide: BorderSide.none), + ), + textInputAction: textInputAction, + ), + ), + SizedBox( + height: getProportionateScreenHeight(16), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/login_regist/default_button.dart b/lib/widgets/login_regist/default_button.dart new file mode 100644 index 0000000..7ff7092 --- /dev/null +++ b/lib/widgets/login_regist/default_button.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:provider/provider.dart'; + +import '../../theme.dart'; +import '../../size_config.dart'; + +class DefaultButton extends StatelessWidget { + const DefaultButton({ + Key? key, + this.text = '', + this.weight = semiBold, + this.press, + this.isCart, + }) : super(key: key); + + final String text; + final FontWeight weight; + final VoidCallback? press; + final bool? isCart; + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return SizedBox( + width: isCart != null + ? getProportionateScreenWidth(120) + : getProportionateScreenWidth(300), + height: isCart != null + ? getProportionateScreenWidth(40) + : getProportionateScreenWidth(44), + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(getProportionateScreenWidth(10))), + backgroundColor: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode), + onPressed: press, + child: Text( + text, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: weight, + color: baruTextutih, + letterSpacing: 0.3), + ), + ), + ); + } +} diff --git a/lib/widgets/login_regist/default_button_payment.dart b/lib/widgets/login_regist/default_button_payment.dart new file mode 100644 index 0000000..8d96d45 --- /dev/null +++ b/lib/widgets/login_regist/default_button_payment.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:provider/provider.dart'; + +import '../../theme.dart'; +import '../../size_config.dart'; + +class DefaultButtonPayment extends StatelessWidget { + const DefaultButtonPayment({ + Key? key, + this.text = '', + this.weight = semiBold, + this.press, + this.isCart, + this.width, + this.height, + this.isInvoice, + }) : super(key: key); + + final String text; + final FontWeight weight; + final VoidCallback? press; + final bool? isCart; + final bool? isInvoice; + final double? width; + final double? height; + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return isInvoice == null + ? SizedBox( + width: getProportionateScreenWidth(width ?? 0), + height: getProportionateScreenWidth(height ?? 0), + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + getProportionateScreenWidth(5), + ), + ), + backgroundColor: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + onPressed: press, + child: Text( + text, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: weight, + color: baruTextutih, + letterSpacing: 0.3), + ), + ), + ) + : SizedBox( + width: getProportionateScreenWidth(width ?? 0), + height: getProportionateScreenWidth(height ?? 0), + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + getProportionateScreenWidth(5), + ), + ), + backgroundColor: primaryColor, + ), + onPressed: press, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + Icons.file_download_outlined, + color: baruTextutih, + size: getProportionateScreenWidth(24), + ), + SizedBox(width: getProportionateScreenWidth(3)), + Text( + text, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: weight, + color: baruTextutih, + letterSpacing: 0.3), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/login_regist/default_icon_button.dart b/lib/widgets/login_regist/default_icon_button.dart new file mode 100644 index 0000000..99e4aa3 --- /dev/null +++ b/lib/widgets/login_regist/default_icon_button.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import '../../theme.dart'; +import '../../size_config.dart'; + +class DefaultIconButton extends StatelessWidget { + const DefaultIconButton( + {Key? key, + required this.icon, + required this.iconWidth, + required this.iconHeight, + this.text = '', + this.press, + this.color}) + : super(key: key); + final String icon; + final String text; + final VoidCallback? press; + final double iconHeight; + final double iconWidth; + final Color? color; + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + return SizedBox( + width: double.infinity, + height: getProportionateScreenWidth(50), + child: TextButton.icon( + style: TextButton.styleFrom( + foregroundColor: Colors.white, + side: BorderSide( + width: getProportionateScreenWidth(0.15), + color: Theme.of(context).colorScheme.onBackground, + ), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(getProportionateScreenWidth(10))), + + // backgroundColor: tenthColor, + ), + onPressed: press, + icon: Padding( + padding: EdgeInsets.only(right: getProportionateScreenWidth(1.0)), + child: Image.asset( + icon, + height: getProportionateScreenWidth(iconHeight), + width: getProportionateScreenWidth(iconWidth), + color: color, + ), + ), + label: Text( + text, + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + fontWeight: reguler, + color: Theme.of(context).colorScheme.onBackground, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/login_regist/failed_login.dart b/lib/widgets/login_regist/failed_login.dart new file mode 100644 index 0000000..9827b67 --- /dev/null +++ b/lib/widgets/login_regist/failed_login.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; + +class FailedLogin extends StatelessWidget { + const FailedLogin( + {Key? key, this.text = '', required this.style, this.jarak = 8}) + : super(key: key); + final String text; + final TextStyle style; + final double jarak; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Image.asset( + // 'assets/images/VOCASIA logo.png', + // width: getProportionateScreenWidth(150), + // height: getProportionateScreenHeight(50), + // ), + // SizedBox(height: getProportionateScreenHeight(jarak)), + Padding( + padding: + EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + child: Text(text, + maxLines: 2, textAlign: TextAlign.center, style: style), + ), + ], + ); + } +} diff --git a/lib/widgets/login_regist/footer.dart b/lib/widgets/login_regist/footer.dart new file mode 100644 index 0000000..3cf76ac --- /dev/null +++ b/lib/widgets/login_regist/footer.dart @@ -0,0 +1,64 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/terms_and_privacy.dart'; +import 'package:provider/provider.dart'; + +class Footer extends StatelessWidget { + const Footer( + {Key? key, + required this.textOne, + required this.textTwo, + required this.route, + this.height = 48.0}) + : super(key: key); + final String textOne; + final String textTwo; + final String route; + final double height; + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return Column( + children: [ + Container( + // padding: EdgeInsets.only( + // left: getProportionateScreenWidth(5), + // top: getProportionateScreenWidth(5)), + alignment: Alignment.center, + child: RichText( + text: TextSpan( + children: <TextSpan>[ + TextSpan( + text: textOne, + style: thirdTextStyle.copyWith( + color: fifteenColor, + ), + ), + TextSpan( + text: textTwo, + style: primaryTextStyle.copyWith( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, fontWeight: bold), + recognizer: TapGestureRecognizer() + ..onTap = () { + Navigator.of(context).pushNamedAndRemoveUntil( + route, (Route<dynamic> route) => false); + //Navigator.pushNamed(context, route); + }), + ], + style: primaryTextStyle.copyWith( + color: tenthColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(14), + ), + ), + ), + ), + SizedBox(height: getProportionateScreenHeight(35)), + ], + ); + } +} diff --git a/lib/widgets/login_regist/header.dart b/lib/widgets/login_regist/header.dart new file mode 100644 index 0000000..dffa949 --- /dev/null +++ b/lib/widgets/login_regist/header.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class Header extends StatelessWidget { + const Header( + {Key? key, + this.text = '', + this.text2 = '', + required this.style, + this.jarak = 20, + this.title = true}) + : super(key: key); + final String text; + final String text2; + final TextStyle style; + final double jarak; + final bool title; + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + title == true + ? Image.asset( + isDarkMode + ? 'assets/images/VOCASIA logo.png' + : 'assets/images/VOCASIA logo dark.png', + width: getProportionateScreenWidth(150), + height: getProportionateScreenHeight(50), + ) + : Text(text2, + textAlign: TextAlign.center, + style: thirdTextStyle.copyWith( + color: isDarkMode ? baruTextutih : baruTexthitam, + fontWeight: bold, + fontSize: getProportionateScreenWidth(20), + letterSpacing: 0.23)), + SizedBox(height: getProportionateScreenHeight(jarak)), + // Padding( + // padding: + // EdgeInsets.symmetric(horizontal: getProportionateScreenWidth(16)), + // child: Text(text, + // maxLines: 2, + // textAlign: TextAlign.justify, + // style: thirdTextStyle.copyWith( + // color: isDarkMode ? baruTexthitam : baruTextutih, + // )), + // ), + ], + ); + } +} diff --git a/lib/widgets/login_regist/loading_button.dart b/lib/widgets/login_regist/loading_button.dart new file mode 100644 index 0000000..bb68647 --- /dev/null +++ b/lib/widgets/login_regist/loading_button.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +import '../../theme.dart'; +import '../../size_config.dart'; + +class LoadingButton extends StatelessWidget { + const LoadingButton( + {Key? key, + required this.backgroundButtonColor, + required this.textButtonColor}) + : super(key: key); + + final Color backgroundButtonColor; + final Color textButtonColor; + + @override + Widget build(BuildContext context) { + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + return SizedBox( + width: double.infinity, + height: getProportionateScreenWidth(44), + child: TextButton( + style: TextButton.styleFrom( + foregroundColor: Colors.black, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(getProportionateScreenWidth(10))), + backgroundColor: backgroundButtonColor, + ), + onPressed: () {}, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 17, + height: 17, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Color(0xffFFFFFF), + ), + ), + ), + SizedBox( + width: getProportionateScreenWidth(6), + ), + Text( + 'Loading', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + fontWeight: semiBold, + color: baruTextutih, + letterSpacing: 0.3), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/my_course_list.dart b/lib/widgets/my_course_list.dart new file mode 100644 index 0000000..19b55ae --- /dev/null +++ b/lib/widgets/my_course_list.dart @@ -0,0 +1,686 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/my_course_model.dart'; +import 'package:initial_folder/providers/current_lesson_provider.dart'; +import 'package:initial_folder/providers/detail_course_provider.dart'; +import 'package:initial_folder/providers/instructor_provider.dart'; +import 'package:initial_folder/providers/lesson_course_provider.dart'; +import 'package:initial_folder/providers/my_course_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/course/play_course_page.dart'; +import 'package:initial_folder/services/course_service.dart'; +import 'package:initial_folder/services/instructor_service.dart'; +import 'package:initial_folder/services/lesson_course_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/providers/posting_review_provider.dart' + as postReviewProvider; + +class MyCourseList extends StatefulWidget { + const MyCourseList({ + Key? key, + required this.dataMyCourseModel, + this.onDialogClose, + }) : super(key: key); + + final DataMyCourseModel dataMyCourseModel; + final VoidCallback? onDialogClose; + + @override + State<MyCourseList> createState() => _MyCourseListState(); +} + +class _MyCourseListState extends State<MyCourseList> { + @override + void initState() { + super.initState(); + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + ]); + } + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + final Brightness brightnessValue = + MediaQuery.of(context).platformBrightness; + bool isDarkMode = brightnessValue == Brightness.dark; + double value = 0.0; + if (widget.dataMyCourseModel.rating.length != 0) { + value = double.parse(widget.dataMyCourseModel.rating[0].rating!); + } + TextEditingController _controller = TextEditingController( + text: (widget.dataMyCourseModel.rating.length == 0) + ? "" + : widget.dataMyCourseModel.rating[0].review); + final _formKey = GlobalKey<FormState>(); + return Column( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).colorScheme.primaryContainer, + boxShadow: [ + BoxShadow( + color: isDarkMode ? Colors.black : Colors.grey, + spreadRadius: 0.01, + blurRadius: 2, + offset: Offset(0, 1), + ), + ], + ), + margin: EdgeInsets.only( + left: getProportionateScreenWidth(20), + right: getProportionateScreenWidth(20), + top: getProportionateScreenWidth(10), + bottom: 5), + child: GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailCourseScreen( + resoaktifitas: 'gada', + idcourse: widget.dataMyCourseModel.courseId ?? '1', + ), + ), + ); + }, + child: Padding( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(10), + left: getProportionateScreenWidth(10), + top: getProportionateScreenHeight(10), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(3), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + flex: 10, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + widget.dataMyCourseModel.title ?? ' ', + style: secondaryTextStyle.copyWith( + letterSpacing: 1, + fontWeight: semiBold, + fontSize: SizeConfig.blockHorizontal! * 3.5, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: getProportionateScreenWidth(2)), + Text( + '${widget.dataMyCourseModel.instructor}', + style: thirdTextStyle.copyWith( + fontWeight: light, + color: Colors.grey.shade600, + fontSize: SizeConfig.blockHorizontal! * 3, + ), + ), + SizedBox(height: getProportionateScreenWidth(6)), + ], + ), + ), + SizedBox(width: getProportionateScreenWidth(9)), + Flexible( + flex: 11, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CachedNetworkImage( + placeholder: (context, url) => Shimmer( + child: Container( + color: Colors.black, + ), + gradient: LinearGradient(stops: [ + 0.2, + 0.5, + 0.6 + ], colors: [ + ninthColor, + fourthColor, + ninthColor + ])), + errorWidget: (context, url, error) => + Icon(Icons.error), + imageBuilder: (context, imageUrl) => Container( + width: getProportionateScreenWidth(156), + height: getProportionateScreenWidth(88), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.fill, + image: imageUrl, + ), + ), + ), + imageUrl: widget.dataMyCourseModel.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + ), + SizedBox( + height: getProportionateScreenHeight(8), + ), + ], + ), + ), + ], + ), + ), + Align( + alignment: Alignment.topRight, + child: Text( + 'Progres ${widget.dataMyCourseModel.totalProgress}%', + style: thirdTextStyle.copyWith( + fontSize: SizeConfig.blockHorizontal! * 3.5, + color: Colors.grey.shade600, + letterSpacing: 0.2, + ), + ), + ), + Stack( + children: [ + Container( + width: double.infinity, + height: getProportionateScreenHeight(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.grey.shade300), + ), + Container( + width: (SizeConfig.screenWidth - + getProportionateScreenWidth(32)) * + int.parse(widget.dataMyCourseModel.totalProgress + .toString()) / + 100, + height: getProportionateScreenHeight(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: primaryColor, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenWidth(10)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + RatingBarIndicator( + itemSize: SizeConfig.blockHorizontal! * 3.5, + rating: double.parse(widget + .dataMyCourseModel.rating.isEmpty + ? '0' + : widget.dataMyCourseModel.rating[0].rating ?? + '5'), + direction: Axis.horizontal, + itemCount: 5, + itemBuilder: (context, index) { + if (index < + double.parse( + widget.dataMyCourseModel.rating.isEmpty + ? '0' + : widget.dataMyCourseModel.rating[0] + .rating ?? + '5')) { + return FaIcon( + FontAwesomeIcons.solidStar, + color: primaryColor, + ); + } else { + return FaIcon( + FontAwesomeIcons.star, + color: primaryColor, + ); + } + }, + ), + SizedBox(width: getProportionateScreenWidth(5)), + InkWell( + onTap: () => showDialog( + context: context, + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => postReviewProvider + .PostingReviewProvider( + courseService: CourseService(), + ), + ) + ], + child: Consumer< + postReviewProvider.PostingReviewProvider>( + builder: (context, state, _) { + if (state.state == + postReviewProvider + .ResultState.uninitilized) { + return AlertDialog( + surfaceTintColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(5)), + contentPadding: EdgeInsets.symmetric( + vertical: 10, horizontal: 10), + insetPadding: EdgeInsets.all(1), + backgroundColor: Theme.of(context) + .colorScheme + .background, + title: Center( + child: Text( + 'Berikan Penilaian', + style: thirdTextStyle.copyWith( + fontSize: 15, + fontWeight: reguler, + letterSpacing: 0.2, + color: Theme.of(context) + .colorScheme + .background, + ), + ), + ), + content: Container( + width: + getProportionateScreenWidth(300), + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Form( + key: _formKey, + child: Column( + children: [ + Container( + width: + getProportionateScreenWidth( + 170), + height: + getProportionateScreenWidth( + 100), + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular( + 8), + image: DecorationImage( + fit: BoxFit.fill, + image: NetworkImage(widget + .dataMyCourseModel + .thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg'), + ), + ), + ), + SizedBox( + height: + getProportionateScreenWidth( + 16)), + Text( + widget.dataMyCourseModel + .title ?? + '', + style: thirdTextStyle + .copyWith( + letterSpacing: 1, + fontWeight: + reguler), + textAlign: + TextAlign.center), + SizedBox( + height: + getProportionateScreenWidth( + 23), + ), + RatingBar.builder( + minRating: 1, + itemPadding: + EdgeInsets.only( + left: 9), + initialRating: (widget + .dataMyCourseModel + .rating + .length == + 0) + ? double.parse("0") + : double.parse(widget + .dataMyCourseModel + .rating[0] + .rating!), + itemSize: 25, + maxRating: 5, + itemBuilder: + (context, _) { + return FaIcon( + FontAwesomeIcons + .solidStar, + color: Colors + .orange[300]); + }, + onRatingUpdate: (val) { + value = val; + }), + SizedBox( + height: + getProportionateScreenWidth( + 16)), + SizedBox( + width: + getProportionateScreenWidth( + 250), + child: TextFormField( + validator: (val) { + print(value); + if ((val == null || + val.isEmpty) || + value == 0.0) { + return 'Ulasan dan Rating tidak boleh kosong'; + } + return null; + }, + controller: _controller, + textInputAction: + TextInputAction.done, + cursorColor: + secondaryColor, + scrollPadding: + EdgeInsets.zero, + minLines: 2, + maxLines: null, + decoration: + InputDecoration( + filled: true, + fillColor: Theme.of( + context) + .brightness == + Brightness.dark + ? seventeenColor + : Colors.grey[200], + border: + OutlineInputBorder( + borderRadius: + BorderRadius + .circular( + 10, + ), + borderSide: + BorderSide + .none), + hintStyle: + secondaryTextStyle + .copyWith( + color: secondaryColor, + letterSpacing: 0.5, + fontSize: + getProportionateScreenWidth( + 12), + ), + hintText: + "Tulis Ulasan", + ), + ), + ), + SizedBox( + height: + getProportionateScreenWidth( + 10)), + ElevatedButton( + onPressed: () async { + if (_formKey.currentState! + .validate()) { + await state.postingReview( + _controller.text, + int.parse(widget + .dataMyCourseModel + .courseId + .toString()), + value.toInt()); + await Provider.of< + MyCourseProvider>( + context, + listen: false) + .getMyCourse(); + } + }, + child: Text( + 'Kirim Penilaian', + style: thirdTextStyle + .copyWith( + color: + Colors.white), + ), + style: ElevatedButton + .styleFrom( + minimumSize: Size( + getProportionateScreenWidth( + 150), + getProportionateScreenHeight( + 33)), + backgroundColor: + primaryColor, + ), + ) + ], + ), + ), + ), + ), + ); + } else if (state.state == + postReviewProvider + .ResultState.loading) { + return AlertDialog( + content: Container( + height: + getProportionateScreenHeight(40), + child: Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ), + ), + ); + } else if (state.state == + postReviewProvider + .ResultState.successUpdate) { + _controller.clear(); + // widget.onDialogClose!(); + + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(10), + ), + backgroundColor: Theme.of(context) + .colorScheme + .background, + surfaceTintColor: Colors.transparent, + contentPadding: + EdgeInsets.fromLTRB(12, 26, 22, 15), + content: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight( + 14)), + child: Container( + height: + getProportionateScreenHeight( + 30), + child: Center( + child: Text( + 'Berhasil mengedit ulasan', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + letterSpacing: 0.5), + ), + ), + ), + ), + ); + } else if (state.state == + postReviewProvider + .ResultState.successAdd) { + _controller.clear(); + // widget.onDialogClose!(); + + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(10), + ), + backgroundColor: Theme.of(context) + .colorScheme + .background, + surfaceTintColor: Colors.transparent, + contentPadding: EdgeInsets.fromLTRB( + 12, 26, 22, 15), + content: Padding( + padding: EdgeInsets.only( + bottom: + getProportionateScreenHeight( + 14)), + child: Container( + height: + getProportionateScreenHeight( + 30), + child: Center( + child: Text( + 'Berhasil memberikan ulasan', + style: primaryTextStyle.copyWith( + fontSize: + getProportionateScreenWidth( + 12), + letterSpacing: 0.5), + ), + ), + ), + )); + } else if (state.state == + postReviewProvider.ResultState.failed) { + return AlertDialog( + title: Text( + 'Terjadi Kesalahan', + style: primaryTextStyle, + ), + ); + } + return AlertDialog( + title: Text( + 'Terjadi Kesalahan', + style: primaryTextStyle, + ), + ); + }, + ), + ), + ), + child: widget.dataMyCourseModel.rating.isEmpty + ? Text( + 'Beri Penilaian', + style: thirdTextStyle.copyWith( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontSize: + getProportionateScreenHeight(10), + ), + ) + : Text( + 'Edit Penilaian', + style: thirdTextStyle.copyWith( + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontSize: + getProportionateScreenHeight(10), + ), + ), + ) + ], + ), + SizedBox(), + ElevatedButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => MultiProvider( + providers: [ + ChangeNotifierProvider( + create: (context) => + CurrentLessonProvider(), + ), + ChangeNotifierProvider( + create: (context) => LessonCourseProvider( + lessonCourseService: + LessonCourseService(), + id: int.parse( + widget.dataMyCourseModel.courseId ?? + '0'), + ), + ), + ChangeNotifierProvider( + create: (context) => DetailCourseProvider( + courseService: CourseService(), + id: widget.dataMyCourseModel.courseId ?? + '1'), + ), + ChangeNotifierProvider( + create: (context) => InstructorProvider( + instructorService: InstructorService(), + id: int.parse(widget + .dataMyCourseModel.instructorId!), + ), + ), + ], + child: PlayCourse( + judul: widget.dataMyCourseModel.title ?? '', + instruktur: + widget.dataMyCourseModel.instructor ?? '', + thumbnail: widget + .dataMyCourseModel.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + courseeid: + widget.dataMyCourseModel.courseId ?? '', + ), + ), + ), + ); + }, + child: Text( + 'Mulai', + style: thirdTextStyle.copyWith( + color: baruTextutih, + ), + ), + style: ElevatedButton.styleFrom( + minimumSize: Size(getProportionateScreenWidth(100), + getProportionateScreenHeight(33)), + backgroundColor: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10))), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/notifikasi_list.dart b/lib/widgets/notifikasi_list.dart new file mode 100644 index 0000000..e494f76 --- /dev/null +++ b/lib/widgets/notifikasi_list.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; + +class NotifikasiList extends StatelessWidget { + const NotifikasiList( + {Key? key, this.berhasil = true, this.baru = false, required this.data}) + : super(key: key); + final bool berhasil; + final bool baru; + final HistoryTransactionModel data; + @override + Widget build(BuildContext context) { + return Container( + color: (baru == false) ? Color(0xFF181818) : Color(0xFF212121), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 12), + (data.statusPayment == 'Success') + ? Row( + children: [ + Container( + alignment: Alignment.center, + width: getProportionateScreenWidth(120), + height: getProportionateScreenWidth(24), + child: Text( + 'Berhasil Dibayar', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: Colors.green[900], + fontSize: SizeConfig.blockHorizontal! * 2.6, + fontWeight: semiBold), + ), + decoration: BoxDecoration( + color: Colors.green[300], + borderRadius: BorderRadius.circular(5), + //border: Border.all(color: kPrimaryColor), + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Icon(Icons.lens_rounded, + color: Color(0xffc4c4c4), + size: getProportionateScreenWidth(7)), + SizedBox(width: getProportionateScreenWidth(8)), + Text( + DateFormat('Hm').format(data.date!), + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + letterSpacing: 0.5, + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + (baru == true) + ? Icon(Icons.lens_rounded, + color: Color(0xffCD2228), + size: getProportionateScreenWidth(12)) + : SizedBox(height: 0), + ], + ) + : Row( + children: [ + Container( + alignment: Alignment.center, + width: getProportionateScreenWidth(165), + height: getProportionateScreenWidth(24), + child: Text( + 'Menunggu Pembayaran', + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: Colors.amber[900], + fontSize: SizeConfig.blockHorizontal! * 2.6, + fontWeight: semiBold), + ), + decoration: BoxDecoration( + color: Colors.amber[200], + borderRadius: BorderRadius.circular(5), + //border: Border.all(color: kPrimaryColor), + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Icon(Icons.lens_rounded, + color: Color(0xffc4c4c4), + size: getProportionateScreenWidth(7)), + SizedBox(width: getProportionateScreenWidth(8)), + Text( + DateFormat('Hm').format(data.date!), + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + letterSpacing: 0.5, + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + (baru == true) + ? Icon(Icons.lens_rounded, + color: Color(0xffCD2228), + size: getProportionateScreenWidth(12)) + : SizedBox(height: 0), + ], + ), + SizedBox(height: 9), + Text( + data.courses![0].title!, + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(13), + letterSpacing: 0.5, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 9), + (data.statusPayment == 'Success') + ? Text( + "Pembayaran berhasil dilakukan, kamu dapat mengikuti kursus sekarang!", + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + letterSpacing: 0.5, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ) + : Text( + "Selesaikan pembayaran sebelum ${DateFormat('E, d MMMM y').format(data.date!)} untuk mulai kursus", + style: primaryTextStyle.copyWith( + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + letterSpacing: 0.5, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 13), + ], + ), + ), + Divider( + height: 1, + color: fourthColor, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/point_istruktur.dart b/lib/widgets/point_istruktur.dart new file mode 100644 index 0000000..58835ee --- /dev/null +++ b/lib/widgets/point_istruktur.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +import '../size_config.dart'; +import '../theme.dart'; + +class PointInstruktur extends StatelessWidget { + const PointInstruktur({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + SizedBox( + width: getProportionateScreenWidth(11), + ), + CircleAvatar( + backgroundColor: primaryColor, + maxRadius: 2, + ), + SizedBox( + width: getProportionateScreenWidth(10), + ), + Text( + 'Instruktur', + style: primaryTextStyle.copyWith(fontSize: 10, color: primaryColor), + ) + ], + ); + } +} diff --git a/lib/widgets/q_and_a.dart b/lib/widgets/q_and_a.dart new file mode 100644 index 0000000..20ee6e3 --- /dev/null +++ b/lib/widgets/q_and_a.dart @@ -0,0 +1,123 @@ +// import 'package:flutter/material.dart'; +// import 'package:initial_folder/models/announcement_model.dart'; +// import 'package:initial_folder/screens/course/component/detail_quest_and_answer.dart'; +// import 'package:initial_folder/theme.dart'; + +// import '../size_config.dart'; + +// class QandA extends StatelessWidget { +// const QandA( +// {Key? key, +// this.divider, +// this.pointInstruktur = const SizedBox(), +// required this.announcementDataModel}) +// : super(key: key); +// final Widget? divider; +// final Widget pointInstruktur; +// final AnnouncementDataModel announcementDataModel; +// @override +// Widget build(BuildContext context) { +// return Container( +// margin: EdgeInsets.symmetric( +// horizontal: getProportionateScreenWidth(16), +// ), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// InkWell( +// onTap: () { +// print(announcementDataModel.idAnnouncement); +// }, +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Row( +// children: [ +// CircleAvatar( +// backgroundColor: primaryColor, +// ), +// SizedBox( +// width: getProportionateScreenWidth(8), +// ), +// Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Row( +// children: [ +// Text( +// announcementDataModel.instructorName ?? ' ', +// style: primaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(12), +// color: tenthColor), +// ), +// pointInstruktur, +// ], +// ), +// Text( +// announcementDataModel.date ?? '', +// style: primaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(12), +// color: secondaryColor), +// ), +// ], +// ), +// ], +// ), +// SizedBox(height: getProportionateScreenHeight(3)), +// Text( +// announcementDataModel.bodyContent ?? '', +// style: secondaryTextStyle.copyWith( +// color: Color(0xffFFFFFF), +// letterSpacing: 1, +// fontSize: SizeConfig.blockHorizontal! * 3.4), +// ), +// SizedBox(height: getProportionateScreenHeight(16)), +// ], +// ), +// ), +// Row( +// children: [ +// Icon( +// Icons.favorite_border, +// color: secondaryColor, +// size: 12, +// ), +// SizedBox( +// width: getProportionateScreenWidth(3), +// ), +// Text( +// announcementDataModel.likes ?? '', +// style: secondaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(10), +// letterSpacing: 0.3), +// ), +// SizedBox( +// width: getProportionateScreenWidth(13), +// ), +// Icon( +// Icons.question_answer_rounded, +// color: secondaryColor, +// size: 12, +// ), +// SizedBox( +// width: getProportionateScreenWidth(3), +// ), +// Text( +// '50', +// style: secondaryTextStyle.copyWith( +// fontSize: getProportionateScreenWidth(10), +// letterSpacing: 0.3), +// ) +// ], +// ), +// SizedBox( +// height: getProportionateScreenWidth(13), +// ), +// SizedBox( +// child: divider, +// ) +// ], +// ), +// ); +// } +// } diff --git a/lib/widgets/qna_user.dart b/lib/widgets/qna_user.dart new file mode 100644 index 0000000..2f44034 --- /dev/null +++ b/lib/widgets/qna_user.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:initial_folder/main.dart'; +import 'package:initial_folder/models/comment_qna_model.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/providers/like_or_unlike_provider.dart'; +import 'package:initial_folder/providers/posting_qna_provider.dart'; +import 'package:initial_folder/providers/qna_provider.dart'; +import 'package:initial_folder/screens/course/component/detail_quest_and_answer.dart'; +import 'package:initial_folder/widgets/counter_qna_comment_page.dart'; +import 'package:initial_folder/widgets/counter_qna_like_page.dart'; +import 'package:initial_folder/widgets/edit_qna_user.dart'; +import 'package:initial_folder/widgets/qna_user_page.dart'; +import 'package:provider/provider.dart'; + +import '../get_it.dart'; +import '../size_config.dart'; +import '../theme.dart'; + +class QnaUser extends StatefulWidget { + const QnaUser({ + Key? key, + required this.id, + this.divider, + required this.qnaDataModel, + required this.index, + required this.userId, + }) : super(key: key); + + final Widget? divider; + final id; + final QnaDataModel qnaDataModel; + final int index; + final int userId; + + @override + State<QnaUser> createState() => _QnaUserState(); +} + +class _QnaUserState extends State<QnaUser> { + double value = 0; + final provider = qnaGetIt<QnaProvider>(); + // bool selfLike = false; + @override + Widget build(BuildContext context) { + PostingQnaProvider deleteQnaProvider = + Provider.of<PostingQnaProvider>(context); + + deleteQna() async { + if (await deleteQnaProvider + .deleteQna(int.parse(widget.qnaDataModel.idQna.toString()))) { + ScaffoldMessenger.of(globalScaffoldKey.currentContext!).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Pertanyaan berhasil dihapus', + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + action: SnackBarAction( + label: 'Lihat', + onPressed: () { + ScaffoldMessenger.of(globalScaffoldKey.currentContext!) + .hideCurrentSnackBar(); + }, + ), + ), + ); + } else { + ScaffoldMessenger.of(globalScaffoldKey.currentContext!).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Terjadi kesalahan', + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } + } + + LikeOrUnlikeProvider _likeOrUnlikeProvider = + Provider.of<LikeOrUnlikeProvider>(context); + + likeOrUnlikes(int idQna) async { + final provider = qnaGetIt<QnaProvider>(); + if (await _likeOrUnlikeProvider.likeOrUnlike(idQna)) { + provider.getQna(widget.id); + print("Respon Baik"); + } + } + + return Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => DetailQuestAndAnswer( + qnaDataModel: widget.qnaDataModel, + id: widget.id, + index: widget.index, + userId: widget.userId, + ), + ), + ).then((value) => provider.getQna(widget.id)); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CircleAvatar( + backgroundColor: primaryColor, + backgroundImage: widget.qnaDataModel.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage(widget.qnaDataModel.fotoProfile ?? '') + as ImageProvider, + ), + SizedBox( + width: getProportionateScreenWidth(8), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + widget.qnaDataModel.username ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(13), + color: Theme.of(context) + .colorScheme + .onBackground), + ), + ], + ), + Text( + widget.qnaDataModel.date ?? '', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: + Theme.of(context).colorScheme.onBackground), + ), + ], + ), + int.parse(widget.qnaDataModel.sender.toString()) == + widget.userId + ? Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + PopupMenuButton( + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Icon( + Icons.more_vert, + color: Theme.of(context) + .colorScheme + .onBackground, + ), + ), + itemBuilder: (context) => [ + PopupMenuItem( + child: Container( + child: Text('Edit'), + ), + value: 'edit', + ), + PopupMenuItem( + child: Text('Hapus'), + value: 'hapus', + ), + ], + onSelected: (value) { + switch (value) { + case 'edit': + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditQna( + quest: widget.qnaDataModel.quest, + title: widget.qnaDataModel.title, + id_qna: widget.qnaDataModel.idQna, + id_course: widget.id, + id_lesson:widget.qnaDataModel.idLesson, + ), + ), + ); + break; + case 'hapus': + deleteQna(); + break; + } + }, + ), + ], + ), + ) + : SizedBox( + height: 12, + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(6)), + widget.qnaDataModel.title != '' + ? Text( + widget.qnaDataModel.title!, + style: secondaryTextStyle.copyWith( + color: Theme.of(context).colorScheme.onBackground, + fontSize: SizeConfig.blockHorizontal! * 4.2, + fontWeight: FontWeight.bold, + ), + ) + : const SizedBox(), + + Html( + data: widget.qnaDataModel.quest ?? '', + style: { + "*": Style(margin: Margins.zero), + }, + ), + + // Text( + // widget.qnaDataModel.quest ?? '', + // style: secondaryTextStyle.copyWith( + // color: Theme.of(context).colorScheme.onBackground, + // letterSpacing: 1, + // fontSize: SizeConfig.blockHorizontal! * 3.4), + // ), + SizedBox(height: getProportionateScreenHeight(16)), + ], + ), + ), + Row( + children: [ + kLike( + widget.qnaDataModel.selfLiked == true + ? Icons.favorite + : Icons.favorite_border_rounded, + widget.qnaDataModel.selfLiked == true + ? Colors.red + : secondaryColor, () { + likeOrUnlikes(int.parse(widget.qnaDataModel.idQna.toString())); + }), + SizedBox( + width: getProportionateScreenWidth(3), + ), + Text( + "${widget.qnaDataModel.countLike ?? 0}", + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), + letterSpacing: 0.3), + ), + SizedBox( + width: getProportionateScreenWidth(13), + ), + kComment( + widget.qnaDataModel.comment.length, + ), + ], + ), + SizedBox( + height: getProportionateScreenWidth(5), + ), + Divider( + thickness: 0.3, + ) + // SizedBox( + // child: widget.divider, + // ) + ], + ), + ); + } +} + +Widget kLike(IconData icon, Color color, Function onTap) { + return GestureDetector( + onTap: () => onTap(), + child: Row( + children: [ + Icon( + icon, + color: color, + size: 12, + ), + ], + ), + ); +} + +Widget kComment(int value) { + return Row( + children: [ + Icon( + FontAwesomeIcons.comment, + color: secondaryColor, + size: 12, + ), + SizedBox( + width: getProportionateScreenWidth(3), + ), + Text( + "$value", + style: secondaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(10), letterSpacing: 0.3), + ) + ], + ); +} diff --git a/lib/widgets/qna_user_page.dart b/lib/widgets/qna_user_page.dart new file mode 100644 index 0000000..530543a --- /dev/null +++ b/lib/widgets/qna_user_page.dart @@ -0,0 +1,131 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/helper/user_info.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/providers/qna_provider.dart'; +import 'package:initial_folder/widgets/qna_user.dart'; +import '../get_it.dart'; + +import '../theme.dart'; + +class QnaUserPage extends StatefulWidget { + const QnaUserPage({Key? key, required this.idCourse}) : super(key: key); + final idCourse; + + @override + State<QnaUserPage> createState() => _QnaUserPageState(); +} + +class _QnaUserPageState extends State<QnaUserPage> { + final provider = qnaGetIt<QnaProvider>(); + int? userId = 0; + + void getUserId() async { + userId = await UsersInfo().getIdUser(); + } + + @override + void initState() { + // TODO: implement initState + getUserId(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + provider.getQna(widget.idCourse); + late Widget build; + + return StreamBuilder<QnaModel>( + stream: provider.qnaStream, + builder: (context, AsyncSnapshot<QnaModel> snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + build = Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + break; + case ConnectionState.none: + build = Center( + child: Text( + 'Tidak ada koneksi', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.active: + if (snapshot.data!.data[0].isEmpty) { + build = Center( + child: Text( + 'Tidak Ada Pertanyaan', + style: thirdTextStyle, + ), + ); + } else { + print('masuk siniiiiiiiiiii active'); + // build = Text(snapshot.data!.status.toString()); + build = ListView.builder( + itemCount: snapshot.data!.data[0].length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemBuilder: (context, index) { + return QnaUser( + divider: Divider(), + qnaDataModel: snapshot.data!.data[0][index], + id: widget.idCourse, + index: index, + userId: userId!, + ); + }, + ); + } + break; + case ConnectionState.done: + if (snapshot.data!.data[0].isEmpty) { + build = Center( + child: Text( + 'Belum ada pertanyaan', + style: thirdTextStyle, + ), + ); + } else { + print('masuk siniiiiiiiiiii done'); + + build = ListView.builder( + itemCount: snapshot.data!.data[0].length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemBuilder: (context, index) { + return QnaUser( + divider: Divider( + thickness: 3.0, + ), + qnaDataModel: snapshot.data!.data[0][index], + id: widget.idCourse, + index: index, + userId: userId!, + ); + }, + ); + } + break; + } + } + + return build; + }, + ); + } +} diff --git a/lib/widgets/reply_announcement_user.dart b/lib/widgets/reply_announcement_user.dart new file mode 100644 index 0000000..ef719f1 --- /dev/null +++ b/lib/widgets/reply_announcement_user.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/models/reply_announcement_model.dart'; +import '../size_config.dart'; +import '../theme.dart'; + +class ReplyAnnouncementUser extends StatefulWidget { + const ReplyAnnouncementUser( + {Key? key, + required this.announcementDataModel, + required this.divider, + required this.replyModel, + required this.userId}) + : super(key: key); + + final Widget? divider; + final ReplyModel replyModel; + final int userId; + final AnnouncementDataModel announcementDataModel; + @override + State<ReplyAnnouncementUser> createState() => _ReplyAnnouncementUserState(); +} + +class _ReplyAnnouncementUserState extends State<ReplyAnnouncementUser> { + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 6, + ), + Row( + children: [ + CircleAvatar( + backgroundColor: primaryColor, + backgroundImage: widget.replyModel.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage(widget.replyModel.fotoProfile ?? '') + as ImageProvider, + ), + SizedBox(width: getProportionateScreenWidth(8)), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () { + print( + 'Print ${widget.announcementDataModel.tokenAnnouncement}'); + }, + child: Text( + widget.replyModel.name ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context) + .colorScheme + .onBackground), + ), + ), + ], + ), + Text( + widget.replyModel.createAt ?? '', + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).colorScheme.onBackground), + ), + ], + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Text( + widget.replyModel.body ?? '', + style: thirdTextStyle.copyWith( + color: Theme.of(context).colorScheme.onBackground, + letterSpacing: 1, + fontSize: SizeConfig.blockHorizontal! * 3.4), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(6)), + // SizedBox(child: widget.divider) + Divider( + thickness: 0.3, + ) + ], + ), + ); + } +} diff --git a/lib/widgets/reply_announcement_user_page.dart b/lib/widgets/reply_announcement_user_page.dart new file mode 100644 index 0000000..5f2d569 --- /dev/null +++ b/lib/widgets/reply_announcement_user_page.dart @@ -0,0 +1,119 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/announcement_model.dart'; +import 'package:initial_folder/providers/reply_announcement_provider.dart'; +import 'package:initial_folder/widgets/reply_announcement_user.dart'; + +import '../get_it.dart'; +import '../theme.dart'; + +class ReplyAnnouncementUserPage extends StatefulWidget { + const ReplyAnnouncementUserPage({ + Key? key, + required this.idCourse, + required this.index, + required this.userId, + }) : super(key: key); + final idCourse; + final int index; + final int userId; + + @override + State<ReplyAnnouncementUserPage> createState() => + _ReplyAnnouncementUserPageState(); +} + +class _ReplyAnnouncementUserPageState extends State<ReplyAnnouncementUserPage> { + final provider = replyAnnouncementGetIt<ReplyAnnouncementProvider>(); + + @override + Widget build(BuildContext context) { + provider.getReplyAnnouncement(widget.idCourse, widget.index); + late Widget build; + + return StreamBuilder<AnnouncementModel>( + stream: provider.replyAnnouncementStream, + builder: (context, AsyncSnapshot<AnnouncementModel> snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + build = Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + break; + case ConnectionState.none: + build = Center( + child: Text( + 'Tidak ada koneksi', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.active: + build = snapshot.data!.data[0][widget.index].replies.length > 0 + ? ListView.builder( + itemCount: + snapshot.data!.data[0][widget.index].replies.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + var replyModel = + snapshot.data!.data[0][widget.index].replies[index]; + var announcementuser = + snapshot.data!.data[0][widget.index]; + return ReplyAnnouncementUser( + divider: Divider(), + replyModel: replyModel, + announcementDataModel: announcementuser, + userId: widget.userId, + ); + }) + : Center( + child: Text( + 'Belum ada pengumuman', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.done: + build = snapshot.data!.data[0][widget.index].replies.length > 0 + ? ListView.builder( + itemCount: + snapshot.data!.data[0][widget.index].replies.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + var replyModel = + snapshot.data!.data[0][widget.index].replies[index]; + var announcementuser = + snapshot.data!.data[0][widget.index]; + return ReplyAnnouncementUser( + divider: Divider(), + replyModel: replyModel, + announcementDataModel: announcementuser, + userId: widget.userId, + ); + }) + : Center( + child: Text( + 'Belum ada pertanyaan', + style: thirdTextStyle, + ), + ); + break; + } + } + return build; + }, + ); + } +} diff --git a/lib/widgets/reply_qna_user.dart b/lib/widgets/reply_qna_user.dart new file mode 100644 index 0000000..342de99 --- /dev/null +++ b/lib/widgets/reply_qna_user.dart @@ -0,0 +1,215 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; +import 'package:initial_folder/main.dart'; +import 'package:initial_folder/models/comment_qna_model.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/providers/posting_qna_reply_provider.dart'; +import 'package:initial_folder/screens/course/component/detail_quest_and_answer.dart'; +import 'package:initial_folder/widgets/edit_reply_qna_user.dart'; +import 'package:provider/provider.dart'; + +import '../size_config.dart'; +import '../theme.dart'; + +class ReplyQnaUser extends StatefulWidget { + const ReplyQnaUser( + {Key? key, + required this.qnaDataModel, + required this.divider, + required this.comment, + required this.userId, + required this.onDeleteReply,}) + : super(key: key); + + final Widget? divider; + final Comment comment; + final int userId; + final QnaDataModel qnaDataModel; + final Function(String idRep) onDeleteReply; + @override + State<ReplyQnaUser> createState() => _ReplyQnaUserState(); +} + +class _ReplyQnaUserState extends State<ReplyQnaUser> { + @override + Widget build(BuildContext context) { + PostingQnaReplyProvider deleteReplyQnaProvider = + Provider.of<PostingQnaReplyProvider>(context); + + deleteReplyQna() async { + if (await deleteReplyQnaProvider.deleteReplyQna(int.parse(widget.comment.idRep!))) { + // Notify the parent widget about the deletion + widget.onDeleteReply(widget.comment.idRep!); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Balasan berhasil dihapus', + style: primaryTextStyle.copyWith( + color: Colors.white, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + } else { + // Handle the error case + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: Duration(seconds: 2), + backgroundColor: primaryColor, + content: Text( + 'Terjadi kesalahan', + style: primaryTextStyle.copyWith( + color: backgroundColor, + ), + ), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ); + } +} + + + return Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 6, + ), + Row( + children: [ + CircleAvatar( + backgroundColor: primaryColor, + backgroundImage: widget.comment.fotoProfile == null + ? AssetImage("assets/images/Profile Image.png") + : NetworkImage(widget.comment.fotoProfile ?? '') + as ImageProvider, + ), + SizedBox( + width: getProportionateScreenWidth(8), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + InkWell( + onTap: () { + print('Print ${widget.qnaDataModel.idQna}'); + }, + child: Text( + widget.comment.username ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context) + .colorScheme + .onBackground), + ), + ), + ], + ), + Text( + widget.comment.createAt ?? '', + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: Theme.of(context).colorScheme.onBackground), + ), + ], + ), + int.parse(widget.comment.sender.toString()) == widget.userId + ? Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + PopupMenuButton( + child: Padding( + padding: EdgeInsets.only(right: 10), + child: Icon( + Icons.more_vert, + color: Theme.of(context) + .colorScheme + .onBackground, + ), + ), + itemBuilder: (context) => [ + PopupMenuItem( + child: Container( + child: Text('Edit'), + ), + value: 'edit', + ), + PopupMenuItem( + child: Text('Hapus'), + value: 'hapus', + ), + ], + onSelected: (value) { + switch (value) { + case 'edit': + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => EditReplyQna( + id_qna: widget.qnaDataModel.idQna, + text_rep: widget.comment.textRep, + id_rep: widget.comment.idRep, + ), + ), + ); + break; + case 'hapus': + print(widget.comment.idRep); + deleteReplyQna(); + break; + } + }, + ), + ], + ), + ) + : SizedBox( + height: 12, + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(8)), + Html( + data: widget.comment.textRep ?? '', + style: { + "*": Style(margin: Margins.zero), + }, + ), + // Text( + // widget.comment.textRep ?? '', + // style: thirdTextStyle.copyWith( + // color: Theme.of(context).colorScheme.onBackground, + // letterSpacing: 1, + // fontSize: SizeConfig.blockHorizontal! * 3.4), + // ), + SizedBox(height: getProportionateScreenHeight(6)), + ], + ), + Divider( + thickness: 0.3, + ) + ], + ), + ); + } +} diff --git a/lib/widgets/reply_qna_user_page.dart b/lib/widgets/reply_qna_user_page.dart new file mode 100644 index 0000000..b4028cb --- /dev/null +++ b/lib/widgets/reply_qna_user_page.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/models/qna_model.dart'; +import 'package:initial_folder/providers/reply_qna_provider.dart'; +import 'package:initial_folder/widgets/reply_qna_user.dart'; + +import '../get_it.dart'; +import '../theme.dart'; + +class ReplyQnaUserPage extends StatefulWidget { + const ReplyQnaUserPage({ + Key? key, + required this.idCourse, + required this.idQna, + required this.userId, required this.onReplyDeleted, + }) : super(key: key); + final idCourse; + final String idQna; + final int userId; + final Function(String idRep) onReplyDeleted; + + + @override + State<ReplyQnaUserPage> createState() => _ReplyQnaUserPageState(); +} + +class _ReplyQnaUserPageState extends State<ReplyQnaUserPage> { + final provider = replyQnaGetIt<ReplyQnaProvider>(); + + @override + Widget build(BuildContext context) { + provider.getReplyQnaById(widget.idCourse, widget.idQna); + late Widget build; + + return StreamBuilder<QnaModel>( + stream: provider.replyQnaStream, + builder: (context, AsyncSnapshot<QnaModel> snapshot) { + if (snapshot.hasError) { + return Center( + child: Text( + 'Terjadi Kesalahan', + style: thirdTextStyle, + ), + ); + } else { + switch (snapshot.connectionState) { + case ConnectionState.waiting: + build = Center( + child: CircularProgressIndicator( + color: primaryColor, + strokeWidth: 2, + ), + ); + break; + case ConnectionState.none: + build = Center( + child: Text( + 'Tidak ada koneksi', + style: thirdTextStyle, + ), + ); + break; + case ConnectionState.active: + // build = snapshot.data!.data[0][widget.index].comment.length > 0 + // ? ListView.builder( + // itemCount: + // snapshot.data!.data[0][widget.index].comment.length, + // shrinkWrap: true, + // physics: NeverScrollableScrollPhysics(), + // itemBuilder: (context, index) { + // var comment = snapshot + // .data!.data[0][widget.index].comment[index]; + // var qnauser = snapshot.data!.data[0][widget.index]; + // return ReplyQnaUser( + // divider: Divider(), + // comment: comment, + // qnaDataModel: qnauser, + // userId: widget.userId, + // ); + // }) + // : Center( + // child: Text( + // 'Belum ada pertanyaan', + // style: thirdTextStyle, + // ), + // ); + // break; + case ConnectionState.done: + if (snapshot.hasData && snapshot.data!.data.isNotEmpty) { + final qnaData = snapshot.data!.data[0]; + return ListView.builder( + itemCount: qnaData.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + var comment = qnaData[index].comment; + return Column( + children: comment.map((commentItem) { + return ReplyQnaUser( + divider: Divider(), + comment: commentItem, + qnaDataModel: qnaData[index], + userId: widget.userId,onDeleteReply: widget.onReplyDeleted, + ); + }).toList(), + ); + }, + ); + } else { + return Center( + child: Text( + 'Belum ada balasan', + style: thirdTextStyle, + ), + ); + } + // break; + } + } + + return build; + }); + } +} diff --git a/lib/widgets/riwayat_list.dart b/lib/widgets/riwayat_list.dart new file mode 100644 index 0000000..0bfbc60 --- /dev/null +++ b/lib/widgets/riwayat_list.dart @@ -0,0 +1,425 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/screens/checkout/snap_payment_page.dart'; +import 'package:initial_folder/services/history_transactions_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class RiwayatList extends StatelessWidget { + RiwayatList({Key? key, required this.dataHistoryTransactionModel}) + : super(key: key); + final HistoryTransactionModel dataHistoryTransactionModel; + + final Map<String, String> status = { + 'null': "Dibatalkan", + '1': 'Berhasil', + '0': 'Transaksi Kursus Gratis', + '2': 'Menunggu Pembayaran', + '-1': 'Pembayaran Ditolak', + '-2': 'Melebihi Batas Waktu', + '-3': 'Dibatalkan', + '-5': 'Belum Pilih Metode Pembayaran', + }; + + final Map<String, String> paymentType = { + 'null': "Dibatalkan", + 'bank transfer': 'Bank Transfer', + 'echannel': 'Bank Transfer', + 'credit card': 'Kartu Kredit', + 'permata': 'Bank Transfer', + 'gopay': 'GoPay', + 'cstore': 'Gerai', + 'free': 'Kursus Gratis', + 'Coupon Free Course': 'Kursus Gratis', + 'qris': 'QRIS' + }; + + Widget _statusLabel(String text, Color color) { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(4), + horizontal: getProportionateScreenWidth(5)), + child: Text( + text, + style: thirdTextStyle.copyWith( + color: baruTextutih, + fontSize: getProportionateScreenWidth(9), + fontWeight: semiBold, + ), + ), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(5), + ), + ); + } + + Widget _customButton({VoidCallback? onTap, String? text}) { + return InkWell( + child: Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border.all(color: Color.fromARGB(120, 18, 140, 126)), + borderRadius: BorderRadius.circular(5)), + padding: EdgeInsets.symmetric(vertical: 15), + child: Center( + child: Text( + text!, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, fontSize: 12, letterSpacing: 0.32), + ), + ), + ), + onTap: onTap, + ); + } + + Widget _listCourse(String? thumbnail, String? title) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + flex: 34, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CachedNetworkImage( + imageUrl: thumbnail == null || thumbnail.isEmpty + ? '$baseUrl/images/default-thumbnail.png' + : thumbnail.startsWith("http") + ? thumbnail + : '$baseUrl/uploads/thumbnail/course_thumbnails/$thumbnail', + imageBuilder: (context, imageProvider) => Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenWidth(43), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.cover, + image: imageProvider, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + color: thirdColor, + ), + gradient: LinearGradient( + stops: [0.4, 0.5, 0.6], + colors: [secondaryColor, thirdColor, secondaryColor], + ), + ), + errorWidget: (context, url, error) => Container( + width: getProportionateScreenWidth(108), + height: getProportionateScreenWidth(50), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage( + 'https://api.vokasia.id/images/default-thumbnail.png'), + ), + ), + ), + ), + ], + ), + ), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 96, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title ?? ' ', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenWidth(10), + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16)), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(4), + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.transparent + : secondaryColor.withOpacity(0.5), + offset: Offset(0, 2), + blurRadius: 2, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + top: getProportionateScreenWidth(16), + left: getProportionateScreenWidth(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + // Order ID + dataHistoryTransactionModel.orderId ?? '', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + Row( + children: [ + Text( + dataHistoryTransactionModel.date != null + ? DateFormat.MMMMEEEEd() + .format(dataHistoryTransactionModel.date!) + : '', + style: thirdTextStyle.copyWith( + color: secondaryColor, + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + SizedBox(width: getProportionateScreenWidth(12)), + if (dataHistoryTransactionModel.statusPayment != null && + (dataHistoryTransactionModel.statusPayment == '1' || + dataHistoryTransactionModel.statusPayment == '0')) + _statusLabel( + status[dataHistoryTransactionModel.statusPayment]! ?? + '', + eightColor) + else if (dataHistoryTransactionModel.statusPayment == '2' || + dataHistoryTransactionModel.statusPayment == '0') + _statusLabel( + status[dataHistoryTransactionModel.statusPayment]! ?? + '', + fiveColor) + else if (dataHistoryTransactionModel.statusPayment != + null && + status.containsKey( + dataHistoryTransactionModel.statusPayment)) + _statusLabel( + status[dataHistoryTransactionModel.statusPayment]!, + sevenColor) + else + _statusLabel("${status['null']}", sevenColor) + ], + ), + ], + ), + ), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(8)), + ), + Container( + margin: EdgeInsets.only( + top: getProportionateScreenWidth(12), + left: getProportionateScreenWidth(8), + right: getProportionateScreenWidth(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Kursus", + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.5, + ), + ), + SizedBox(height: 8), + Column( + children: dataHistoryTransactionModel.courses != null + ? dataHistoryTransactionModel.courses! + .map((course) => + _listCourse(course.thumbnail, course.title)) + .toList() + : [], + ), + ], + ), + ), + Container( + margin: EdgeInsets.only( + top: getProportionateScreenHeight(7), + left: getProportionateScreenWidth(12), + right: getProportionateScreenWidth(20), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + "Jenis Pembayaran", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + Spacer(), + Text( + dataHistoryTransactionModel.paymentDetail != null && + dataHistoryTransactionModel + .paymentDetail!.paymentType != + null && + paymentType.containsKey(dataHistoryTransactionModel + .paymentDetail!.paymentType) + ? paymentType[dataHistoryTransactionModel + .paymentDetail!.paymentType]! + .toUpperCase() + : 'Belum Memilih', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + children: [ + Text( + "Total Pembelian", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + Spacer(), + Text( + "Rp. ${dataHistoryTransactionModel.paymentDetail?.paymentType != "Coupon Free Course" ? dataHistoryTransactionModel.totalPrice?.toString() ?? '' : "0"}", + style: thirdTextStyle.copyWith( + fontWeight: bold, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + fontSize: getProportionateScreenWidth(12), + ), + ), + ], + ), + SizedBox(height: 15), + dataHistoryTransactionModel.statusPayment == '1' && + dataHistoryTransactionModel + .paymentDetail!.paymentType != + 'free' + ? Column( + children: [ + _customButton(text: 'Receipt'), + SizedBox( + height: getProportionateScreenHeight(10), + ), + // _customButton(text: 'Invoice'), + // SizedBox( + // height: getProportionateScreenHeight(20), + // ), + ], + ) + : dataHistoryTransactionModel.statusPayment == '-5' + ? Padding( + padding: const EdgeInsets.only(top: 10, bottom: 15), + child: Column( + children: [ + InkWell( + onTap: () async { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => Center( + child: CircularProgressIndicator(), + ), + ); + + try { + String orderId = dataHistoryTransactionModel.orderId ?? ''; + await HistoryTransactionService().checkTransactionExpiration(orderId); + Navigator.of(context).pop(); + } catch (e) { + Navigator.of(context).pop(); + print('Error saat memeriksa status transaksi: $e'); + } + var redirectUrl = dataHistoryTransactionModel.token; + if (redirectUrl != null && redirectUrl.isNotEmpty) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + transactionToken: redirectUrl, + orderId: dataHistoryTransactionModel.orderId ?? '', + grossAmount: dataHistoryTransactionModel.totalPrice ?? 0, + courseTitle: '', + courseThumbnail: '', + courseInstructor: '', + courseId: '', + ), + ), + ); + } else { + print("Pembayaran belum dimulai atau URL pembayaran tidak ada."); + } + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 15), + child: Center( + child: Text( + 'Lanjutkan Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: 12, + fontWeight: semiBold, + color: Colors.white, + ), + ), + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ), + ), + ], + ), + ) + : Container() + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/riwayat_list_delete.dart b/lib/widgets/riwayat_list_delete.dart new file mode 100644 index 0000000..8da588a --- /dev/null +++ b/lib/widgets/riwayat_list_delete.dart @@ -0,0 +1,688 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/models/history_transaction_model.dart'; +import 'package:initial_folder/providers/detail_invoice_provider.dart'; +import 'package:initial_folder/providers/order_provider.dart'; +import 'package:initial_folder/providers/theme_provider.dart'; +import 'package:initial_folder/providers/total_price_provider.dart'; +import 'package:initial_folder/screens/checkout/batas_bayar.dart'; +import 'package:initial_folder/screens/checkout/gopay/batas_bayar_gopay.dart'; +import 'package:initial_folder/screens/profile/account_sign_in/detail_transaksi.dart'; +import 'package:initial_folder/services/cancel_payment_service.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/login_regist/default_button_payment.dart'; +import 'package:intl/intl.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +import '../screens/checkout/snap_payment_page.dart'; + +class RiwayatListDelete extends StatefulWidget { + RiwayatListDelete({ + Key? key, + required this.dataHistoryTransactionModel, + required this.onPaymentCancelled, + }) : super(key: key); + final HistoryTransactionModel dataHistoryTransactionModel; + final Function(String) onPaymentCancelled; + + @override + State<RiwayatListDelete> createState() => _RiwayatListDeleteState(); +} + +class _RiwayatListDeleteState extends State<RiwayatListDelete> { + bool isLoading = false; + + final Map<String, String> status = { + 'null': "Dibatalkan", + '1': 'Berhasil', + '0': 'Transaksi Kursus Gratis', + '2': 'Menunggu Pembayaran', + '-1': 'Pembayaran Ditolak', + '-2': 'Melebihi Batas Waktu', + }; + + final Map<String, String> paymentType = { + 'null': "Kartu Kredit", + 'credit card': 'Kartu Kredit', + 'qris': 'QRIS', + 'free': 'Kursus Gratis' + }; + + final Map<String, String> bank = { + 'null': "Kartu Kredit", + 'bni': 'BNI Virtual Account', + 'bca': 'BCA Virtual Account', + 'Mandiri': 'Mandiri Virtual Account', + 'Permata': 'Permata Virtual Account', + 'qris': 'QR Code', + 'free': 'Kursus Gratis' + }; + + final Map<String, String> store = { + 'null': "Kartu Kredit", + 'indomaret': 'Indomaret', + 'alfamart': 'Alfamart', + 'free': 'Kursus Gratis' + }; + + Widget _statusLabel(String text, Color color) { + return Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(5), + horizontal: getProportionateScreenWidth(5)), + child: Text( + text, + style: primaryTextStyle.copyWith( + letterSpacing: 0.5, + color: backgroundColor, + fontSize: SizeConfig.blockHorizontal! * 2.5, + fontWeight: semiBold), + ), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(5), + ), + ); + } + + Widget _customButton({VoidCallback? onTap, String? text}) { + return InkWell( + child: Container( + width: double.infinity, + decoration: BoxDecoration( + border: Border.all(color: Color.fromARGB(120, 18, 140, 126)), + borderRadius: BorderRadius.circular(5)), + padding: EdgeInsets.symmetric(vertical: 15), + child: Center( + child: Text( + text!, + style: thirdTextStyle.copyWith( + fontWeight: semiBold, fontSize: 12, letterSpacing: 0.32), + )), + ), + onTap: onTap, + ); + } + + Widget _listCourse(String? thumbnail, String? title) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + flex: 34, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CachedNetworkImage( + imageUrl: thumbnail != null && thumbnail.isNotEmpty + ? '$baseUrl/uploads/thumbnail/course_thumbnails/$thumbnail' + : '$baseUrl/images/default-thumbnail.png', + imageBuilder: (context, imageProvider) => Container( + width: getProportionateScreenWidth(100), + height: getProportionateScreenWidth(43), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.cover, + image: imageProvider, + ), + ), + ), + placeholder: (context, url) => Shimmer( + child: Container( + color: thirdColor, + ), + gradient: LinearGradient(stops: [ + 0.4, + 0.5, + 0.6 + ], colors: [ + secondaryColor, + thirdColor, + secondaryColor + ])), + errorWidget: (context, url, error) => Container( + width: getProportionateScreenWidth(108), + height: getProportionateScreenWidth(50), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + image: DecorationImage( + fit: BoxFit.cover, + image: NetworkImage( + thumbnail == null || thumbnail.isEmpty + ? '$baseUrl/images/default-thumbnail.png' + : thumbnail.startsWith("http") + ? thumbnail + : '$baseUrl/uploads/thumbnail/course_thumbnails/$thumbnail', + )), + ), + ), + ), + ], + )), + SizedBox(width: getProportionateScreenWidth(10)), + Flexible( + flex: 96, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + title ?? ' ', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + var selected = Provider.of<TotalPriceProvider>(context); + var selectedInvoice = Provider.of<DetailInvoiceProvider>(context); + final themeProvider = Provider.of<ThemeProvider>(context); + + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenWidth(10), + left: getProportionateScreenWidth(16), + right: getProportionateScreenWidth(16)), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primaryContainer, + borderRadius: BorderRadius.circular(4), + boxShadow: [ + BoxShadow( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.transparent + : secondaryColor.withOpacity(0.5), + offset: Offset(0, 2), + blurRadius: 2, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: EdgeInsets.only( + top: getProportionateScreenHeight(9), + left: getProportionateScreenWidth(9), + right: getProportionateScreenWidth(9), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "${widget.dataHistoryTransactionModel.orderId!}", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + Text( + "Bayar Sebelum", + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + color: secondaryColor, + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(5)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + DateFormat('dd MMMM yyyy') + .format(widget.dataHistoryTransactionModel.date!), + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + fontFamily: "Poppins", + ), + ), + SizedBox(width: getProportionateScreenWidth(8)), + Container( + decoration: BoxDecoration( + color: sevenColor, + borderRadius: BorderRadius.circular(4), + ), + padding: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(3), + vertical: getProportionateScreenHeight(1), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Icon( + Icons.access_time, + size: getProportionateScreenWidth(14), + color: baruTextutih, + ), + SizedBox(width: getProportionateScreenWidth(2)), + Text( + DateFormat('dd MMMM HH:mm').format(widget + .dataHistoryTransactionModel.date! + .add(Duration(days: 1))), + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(11), + color: baruTextutih, + ), + ), + ], + ), + ), + ], + ), + ], + ), + ), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(8)), + ), + Container( + margin: EdgeInsets.only( + top: getProportionateScreenWidth(12), + left: getProportionateScreenWidth(8), + right: getProportionateScreenWidth(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Kursus", + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(12), + letterSpacing: 0.5, + ), + ), + Column( + children: widget.dataHistoryTransactionModel.courses! + .map((course) => + _listCourse(course.thumbnail, course.title)) + .toList(), + ), + ], + ), + ), + Container( + margin: EdgeInsets.symmetric( + horizontal: getProportionateScreenWidth(10), + vertical: getProportionateScreenHeight(10)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.dataHistoryTransactionModel.paymentDetail?.paymentType != 'qris') ...[ + Row( + children: [ + Text( + "Metode Pembayaran", + style: primaryTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Spacer(), + Text( + widget.dataHistoryTransactionModel.paymentDetail != null && + bank.containsKey(widget.dataHistoryTransactionModel.paymentDetail!.bank) + ? toBeginningOfSentenceCase(bank[widget.dataHistoryTransactionModel.paymentDetail!.bank] ?? '') ?? '' + : (store.containsKey(widget.dataHistoryTransactionModel.paymentDetail!.store) + ? toBeginningOfSentenceCase(store[widget.dataHistoryTransactionModel.paymentDetail!.store] ?? '') ?? '' + : toBeginningOfSentenceCase(paymentType[widget.dataHistoryTransactionModel.paymentDetail!.paymentType] ?? '') ?? ''), + style: primaryTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + SizedBox(height: 8), + Row( + children: [ + Text( + widget.dataHistoryTransactionModel.paymentDetail != null && + bank.containsKey(widget.dataHistoryTransactionModel.paymentDetail!.bank) + ? "Nomor Virtual Account" + : "Kode Pembayaran", + style: primaryTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Spacer(), + if (widget.dataHistoryTransactionModel.paymentDetail?.paymentType != 'qris') + Text( + widget.dataHistoryTransactionModel.paymentDetail != null + ? widget.dataHistoryTransactionModel.paymentDetail!.vaNumber ?? 'QRIS' + : 'QRIS', + style: thirdTextStyle.copyWith( + fontWeight: reguler, + fontSize: getProportionateScreenWidth(11), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + ], + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Total Pembayaran", + style: thirdTextStyle.copyWith( + fontFamily: "Poppins", + fontSize: getProportionateScreenWidth(11), + ), + ), + Spacer(), + Text( + "Rp. ${widget.dataHistoryTransactionModel.totalPrice! < 50000 ? (widget.dataHistoryTransactionModel.totalPrice! + 5000) : widget.dataHistoryTransactionModel.totalPrice}", + style: thirdTextStyle.copyWith( + fontWeight: semiBold, + fontSize: getProportionateScreenWidth(13), + ), + ), + ], + ), + SizedBox(height: getProportionateScreenHeight(10)), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DefaultButtonPayment( + height: 35, + width: 150, + press: () { + // Cek payment type, jika qris langsung arahkan ke SnapPaymentPage + if (widget.dataHistoryTransactionModel.paymentDetail?.paymentType == 'qris') { + var redirectUrl = widget.dataHistoryTransactionModel.token; + if (redirectUrl != null && redirectUrl.isNotEmpty) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + transactionToken: redirectUrl, + orderId: widget.dataHistoryTransactionModel.orderId ?? '', + grossAmount: widget.dataHistoryTransactionModel.totalPrice ?? 0, + courseTitle: '', + courseThumbnail: '', + courseInstructor: '', + courseId: '', + ), + ), + ); + } else { + print("Pembayaran belum dimulai atau URL pembayaran tidak ada."); + } + } else { + selected.selectedTotalPrices = widget.dataHistoryTransactionModel.totalPrice ?? 0; + selectedInvoice.selectedThumbnail = widget.dataHistoryTransactionModel.courses![0].thumbnail; + + Navigator.of(context).push( + CustomNavigator( + child: DetailInvoice( + orderId: widget.dataHistoryTransactionModel.orderId!, + dataHistoryTransactionModel: widget.dataHistoryTransactionModel, + ), + ), + ); + } + }, + text: "Bayar Sekarang", + weight: semiBold, +), + + SizedBox( + width: getProportionateScreenWidth(150), + height: getProportionateScreenHeight(32), + child: TextButton( + onPressed: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + elevation: 0.0, + backgroundColor: + Theme.of(context).colorScheme.background, + contentPadding: EdgeInsets.symmetric( + vertical: getProportionateScreenHeight(20), + horizontal: getProportionateScreenWidth(10), + ), + actionsPadding: EdgeInsets.only( + right: getProportionateScreenWidth(10), + bottom: getProportionateScreenHeight(12), + ), + + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + getProportionateScreenWidth(4)), + ), + content: Text( + 'Apakah Anda yakin ingin membatalkan transaksi?'), + actions: [ + GestureDetector( + child: Text( + 'Ya', + style: TextStyle( + color: primaryColor, + fontSize: + getProportionateScreenWidth(11), + ), + ), + onTap: () async { + Navigator.of(context).pop(); + setState(() { + isLoading = true; + }); + CancelPaymentService + cancelPaymentService = + CancelPaymentService(); + String message = + await cancelPaymentService + .cancelPayment(widget + .dataHistoryTransactionModel + .orderId!); + widget.onPaymentCancelled(message); + setState(() { + isLoading = false; + }); + }, + ), + SizedBox( + width: getProportionateScreenWidth(5)), + GestureDetector( + child: Text( + 'Tidak', + style: TextStyle( + color: primaryColor, + fontSize: + getProportionateScreenWidth(11), + ), + ), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }, + ); + }, + child: isLoading + ? Container( + width: getProportionateScreenWidth(12), + height: getProportionateScreenHeight(10), + child: CircularProgressIndicator( + color: primaryColor, + ), + ) + : Text( + "Batalkan Transaksi", + style: thirdTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + fontWeight: semiBold, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + letterSpacing: 0.5, + ), + ), + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.background, + shape: RoundedRectangleBorder( + side: BorderSide(color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode), + borderRadius: BorderRadius.circular( + getProportionateScreenWidth(5), + ), + ), + backgroundColor: Colors.transparent, + ), + ), + ), + ], + ), + SizedBox(height: 15), + widget.dataHistoryTransactionModel.statusPayment == 'Success' && + widget.dataHistoryTransactionModel.paymentDetail! + .paymentType != + 'free' + ? Column( + children: [ + _customButton(text: 'Receipt'), + SizedBox( + height: getProportionateScreenHeight(10), + ), + _customButton(text: 'Invoice'), + SizedBox( + height: getProportionateScreenHeight(20), + ), + ], + ) + : widget.dataHistoryTransactionModel.statusPayment == + 'Pending' + ? Padding( + padding: const EdgeInsets.only(top: 10, bottom: 15), + child: Column( + children: [ + InkWell( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => widget + .dataHistoryTransactionModel + .paymentDetail! + .paymentType == + 'gopay' + ? BatasBayarGopay() + : BatasBayar( + historyTransactionModel: widget + .dataHistoryTransactionModel, + ))); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 15), + child: Center( + child: Text( + 'Detail Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: 12, fontWeight: semiBold), + )), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: primaryColor), + ), + ), + SizedBox( + height: 10, + ), + InkWell( + onTap: () { + Provider.of<OrderProvider>(context, + listen: false) + .getTotalPrice(widget + .dataHistoryTransactionModel + .totalPrice + .toString()); + + Future.wait(widget + .dataHistoryTransactionModel.courses! + .map((course) async { + Provider.of<OrderProvider>(context, + listen: false) + .addOrder( + id: course.courseId, + discountPrice: course.price, + instructor: course.instructor, + price: course.price, + title: course.title, + imageUrl: ''); + })).whenComplete( + () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SnapPaymentPage( + orderId: widget + .dataHistoryTransactionModel + .orderId!, + grossAmount: widget + .dataHistoryTransactionModel + .totalPrice!, transactionToken: '', courseTitle: '', courseInstructor: '', courseThumbnail: '', courseId: '', + ), + ), + ), + ); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 15), + child: Center( + child: Text( + 'Ubah Metode Pembayaran', + style: thirdTextStyle.copyWith( + fontSize: 12, + fontWeight: semiBold, + color: primaryColor), + )), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: Colors.transparent, + border: + Border.all(color: primaryColor)), + ), + ), + ], + ), + ) + : Container() + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/search_and_filter_course.dart b/lib/widgets/search_and_filter_course.dart new file mode 100644 index 0000000..66104c0 --- /dev/null +++ b/lib/widgets/search_and_filter_course.dart @@ -0,0 +1,436 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_feather_icons/flutter_feather_icons.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/providers/search_provider.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/home/components/body_comp/product_card/product_card.dart'; +import 'package:initial_folder/screens/search_course/component/filter.dart'; +import 'package:initial_folder/widgets/custom_navigator.dart'; +import 'package:initial_folder/widgets/search_not_found.dart'; +import 'package:provider/provider.dart'; +import '../providers/theme_provider.dart'; +import '../size_config.dart'; +import '../theme.dart'; +import 'package:initial_folder/providers/filters_course_provider.dart' + as filterCourseProvider; + +class SearchAndFilterCourse extends StatefulWidget { + const SearchAndFilterCourse({ + Key? key, + }) : super(key: key); + static const routeName = '/search-and-filter'; + + @override + State<SearchAndFilterCourse> createState() => _SearchAndFilterCourseState(); +} + +class _SearchAndFilterCourseState extends State<SearchAndFilterCourse> { + final TextEditingController _controller = TextEditingController(); + + void clearTextField() { + _controller.clear(); + } + + @override + void initState() { + super.initState(); + Provider.of<SearchProvider>(context, listen: false).resetState(); + Provider.of<filterCourseProvider.FilterCourseProvider>(context, + listen: false) + .resetState(); + Provider.of<filterCourseProvider.FilterCourseProvider>(context, + listen: false) + .resetFilter(); + } + + @override + Widget build(BuildContext context) { + final themeProvider = Provider.of<ThemeProvider>(context); + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + appBar: PreferredSize( + preferredSize: Size.fromHeight(getProportionateScreenWidth(57)), + child: AppBar( + scrolledUnderElevation: 0, + backgroundColor: Theme.of(context).colorScheme.background, + leadingWidth: 30, + actions: [ + IconButton( + padding: EdgeInsets.zero, + onPressed: () => Navigator.of(context, rootNavigator: true).push( + CustomNavigator( + child: Filter( + onApplyFilter: clearTextField, + ), + ), + ), + icon: Icon( + Icons.tune_rounded, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + ), + ], + title: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Theme.of(context).brightness == Brightness.dark + ? seventeenColor + : secondaryColor.withOpacity(0.3), + ), + height: 40, + child: Consumer<SearchProvider>( + builder: (context, state, _) => TextField( + controller: _controller, + autofocus: false, + onSubmitted: (value) async { + Provider.of<filterCourseProvider.FilterCourseProvider>( + context, + listen: false) + .isSearchsFalse(); + filterCourseProvider.FilterCourseProvider filterCourseProv = + Provider.of<filterCourseProvider.FilterCourseProvider>( + context, + listen: false); + + state.searchTextFilter = ""; + state.searchText = validatorSearch(value); + await state.initSearchCourse( + price: filterCourseProv.currentIndexPrice, + level: filterCourseProv.levels.join(','), + rating: filterCourseProv.currentIndexRating, + ); + }, + style: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(14), + letterSpacing: 0.5, + ), + onChanged: (value) { + state.searchTextFilter = ""; + state.searchText = validatorSearch(value); + }, + cursorColor: secondaryColor, + decoration: InputDecoration( + border: InputBorder.none, + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: sevenColor), + borderRadius: BorderRadius.circular(10)), + contentPadding: + EdgeInsets.only(top: getProportionateScreenHeight(4)), + prefixIcon: Icon( + FeatherIcons.search, + size: 20, + color: themeProvider.themeData == ThemeClass.darkmode + ?primaryColor : primaryColorligtmode, + ), + hintText: 'Cari Kursus', + hintStyle: primaryTextStyle.copyWith( + fontSize: getProportionateScreenWidth(12), + color: secondaryColor, + letterSpacing: 0.5, + ), + ), + ), + ), + ), + ), + ), + body: Provider.of<filterCourseProvider.FilterCourseProvider>(context) + .isSearch + ? Consumer<filterCourseProvider.FilterCourseProvider>( + builder: (context, state, _) { + SearchProvider searchProvider = + Provider.of<SearchProvider>(context, listen: false); + print("Ini searchan ${searchProvider.search}"); + if (state.state == filterCourseProvider.ResultState.loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.green, + ), + ); + } else if (state.state == + filterCourseProvider.ResultState.hasData) { + return Padding( + padding: + EdgeInsets.only(left: getProportionateScreenWidth(2)), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(14), + top: getProportionateScreenHeight(5), + ), + child: Text( + "Kursus ${searchProvider.searchTextFilter.replaceAll('%', ' ')}", + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + ), + GridView.builder( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(20), + top: getProportionateScreenHeight(20), + bottom: getProportionateScreenHeight(20), + ), + physics: ScrollPhysics(), + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.8 / 4, + crossAxisSpacing: 10, + mainAxisSpacing: 13, + ), + itemCount: state.filterResult.length, + itemBuilder: (context, index) { + var course = state.filterResult[index]; + int price = int.tryParse( + course.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse(course + .discountPrice + .replaceAll('.', '')) ?? + 0; + + int calculatedPrice = + (course.discountPrice != '0') + ? price - discountPrice + : price; + + String displayedPrice = (calculatedPrice == 0) + ? course.price + : calculatedPrice.toString(); + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(11)), + child: ProductCard( + totalDiscount: course.totalDiscount ?? 0, + students: course.students ?? '0', + id: course.idCourse, + thumbnail: course.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: course.title, + instructorName: course.instructorName, + specificRating: double.parse( + course.rating[0]!.avgRating != null + ? '${course.rating[0]!.avgRating}' + : '5.0') + .toString(), + rating: course.rating[0]!.avgRating != null + ? '${course.rating[0]!.avgRating}' + : '5.0', + numberOfRatings: + course.rating[0]!.totalReview ?? '0', + isTopCourse: course.topCourse ?? '0', + price: (course.price == '0') + ? 'Gratis' + : (course.promoPrice != '0') + ? numberFormat(course.promoPrice) + : numberFormat(displayedPrice), + realPrice: (course.price == '0') + ? '' + : numberFormat(course.price), + press: () { + Navigator.of(context, rootNavigator: true) + .push( + CustomNavigator( + child: DetailCourseScreen( + idcourse: course.idCourse, + ), + ), + ); + }, + ), + ); + }, + ), + ], + ), + ), + ); + } else if (state.state == + filterCourseProvider.ResultState.error || + state.state == filterCourseProvider.ResultState.noData) { + return SearchNotFound(); + } else { + return Center(child: Text('')); + } + }, + ) + : Consumer<SearchProvider>( + builder: (context, state, _) { + SearchProvider searchProvider = + Provider.of<SearchProvider>(context, listen: false); + + if (state.state == ResultState.loading) { + return Center( + child: CircularProgressIndicator( + strokeWidth: 2, + color: primaryColor, + ), + ); + } else if (state.state == ResultState.hasData) { + return Padding( + padding: + EdgeInsets.only(left: getProportionateScreenWidth(2)), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only( + left: getProportionateScreenWidth(14), + top: getProportionateScreenHeight(5), + ), + child: searchProvider.searchText.isNotEmpty + ? Text( + "Kursus ${searchProvider.searchText.replaceAll('%', ' ')}", + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ) + : Text( + "Kursus ${searchProvider.searchTextFilter.replaceAll('%', ' ')}", + textAlign: TextAlign.left, + style: thirdTextStyle.copyWith( + letterSpacing: 1, + fontSize: getProportionateScreenWidth(15), + fontWeight: semiBold, + ), + ), + ), + GridView.builder( + padding: EdgeInsets.only( + right: getProportionateScreenWidth(20), + top: getProportionateScreenHeight(20), + bottom: getProportionateScreenHeight(20), + ), + physics: ScrollPhysics(), + shrinkWrap: true, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 2.8 / 4, + crossAxisSpacing: 12, + mainAxisSpacing: 18, + ), + itemCount: state.result.length, + itemBuilder: (context, index) { + var othersCourse = state.result[index]; + + int price = int.tryParse( + othersCourse.price.replaceAll('.', '')) ?? + 0; + int discountPrice = int.tryParse(othersCourse + .discountPrice + .replaceAll('.', '')) ?? + 0; + + int calculatedPrice = + (othersCourse.discountPrice != '0') + ? price - discountPrice + : price; + + String displayedPrice = (calculatedPrice == 0) + ? othersCourse.price.toString() + : calculatedPrice.toString(); + return Container( + margin: EdgeInsets.only( + bottom: getProportionateScreenHeight(11)), + child: ProductCard( + totalDiscount: + othersCourse.totalDiscount ?? 0, + students: othersCourse.students ?? '0', + id: othersCourse.idCourse, + thumbnail: othersCourse.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: othersCourse.title, + instructorName: othersCourse.instructorName, + specificRating: (othersCourse + .rating.isNotEmpty && + othersCourse.rating[0]?.avgRating != + null) + ? othersCourse.rating[0]!.avgRating + .toString() + : '0', + rating: (othersCourse.rating.isNotEmpty && + othersCourse.rating[0]?.avgRating != + null) + ? othersCourse.rating[0]!.avgRating + .toString() + : '5.0', + numberOfRatings: (othersCourse + .rating.isNotEmpty && + othersCourse.rating[0]?.totalReview != + null) + ? othersCourse.rating[0]!.totalReview! + : '0', + isTopCourse: othersCourse.topCourse ?? '0', + price: (othersCourse.price == '0') + ? 'Gratis' + : (othersCourse.promoPrice != '0') + ? numberFormat( + othersCourse.promoPrice) + : numberFormat(displayedPrice), + realPrice: (othersCourse.price == '0') + ? '' + : numberFormat( + othersCourse.price.toString()), + press: () { + print('ini ke detail'); + if (othersCourse.promoPrice != '0') { + Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: (context) => + DetailCourseScreen( + isPromo: true, + idcourse: othersCourse.idCourse, + ), + ), + ); + } else { + Navigator.of(context, rootNavigator: true) + .push( + MaterialPageRoute( + builder: (context) => + DetailCourseScreen( + // isPromo: true, + idcourse: othersCourse.idCourse, + ), + ), + ); + } + }, + ), + ); + }, + ), + ], + ), + ), + ); + } else if (state.state == ResultState.noData || + state.state == ResultState.error) { + return SearchNotFound(); + } else { + return Center(child: Text('')); + } + }, + ), + ); + } +} diff --git a/lib/widgets/search_not_found.dart b/lib/widgets/search_not_found.dart new file mode 100644 index 0000000..a0cb915 --- /dev/null +++ b/lib/widgets/search_not_found.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/screens/home/components/body_comp/latest_course.dart'; +import 'package:initial_folder/screens/home/components/body_comp/populer_course.dart'; +import 'package:initial_folder/size_config.dart'; +import 'package:initial_folder/theme.dart'; + +class SearchNotFound extends StatelessWidget { + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + Center( + child: Container( + child: Image.asset( + color: Theme.of(context).brightness == Brightness.dark + ? baruTextutih + : twelveColor, + 'assets/images/kursuskosong.png', + width: getProportionateScreenHeight(100), + ), + padding: EdgeInsets.only(top: 40, bottom: 16), + ), + ), + Text( + 'Kursus Belum Tersedia', + style: secondaryTextStyle.copyWith( + fontSize: 14, + fontWeight: semiBold, + ), + ), + SizedBox( + height: 5, + ), + Text( + 'Kursus belum tersedia, silahkan cari kursus yang lain', + style: primaryTextStyle.copyWith(fontSize: 12, fontWeight: reguler), + ), + SizedBox( + height: 25, + ), + PopulerCourse(text: "Kursus Teratas"), + LatestCourse(text: "Kursus Terbaru"), + ], + ), + ); + } +} diff --git a/lib/widgets/terms_and_privacy.dart b/lib/widgets/terms_and_privacy.dart new file mode 100644 index 0000000..4ea5f4b --- /dev/null +++ b/lib/widgets/terms_and_privacy.dart @@ -0,0 +1,73 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:initial_folder/theme.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class TermsAndCondition extends StatefulWidget { + static const routeName = '/article_web'; + + final String url; + final String? id; + + const TermsAndCondition({Key? key, required this.url, this.id}) + : super(key: key); + + @override + State<TermsAndCondition> createState() => _TermsAndConditionState(); +} + +class _TermsAndConditionState extends State<TermsAndCondition> { + double progres = 0; + @override + Widget build(BuildContext context) { + final juduls = { + 'sk': Text('Syarat dan Ketentuan'), + 'prv': Text('Kebijakan Privasi'), + 'about': Text('Tentang Vocasia'), + 'ctc': Text('Kontak Kami'), + 'help': Text('Bantuan'), + 'gopay': Text('Gopay'), + }; + final judul = juduls[widget.id]; + + var controller = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int progress) { + // Update loading bar. + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) {}, + onWebResourceError: (WebResourceError error) {}, + onUrlChange: (change) {}, + onNavigationRequest: (NavigationRequest request) { + if (request.url != widget.url) { + // Prevent navigation to other URLs and about:blank + print("Navigation prevented: ${request.url}"); + return NavigationDecision.prevent; + } else { + print("Navigation Accesed: ${request.url}"); + return NavigationDecision.navigate; + } + }, + ), + ) + ..loadRequest(Uri.parse(widget.url)); + + return Scaffold( + appBar: AppBar(title: judul), + body: Column( + children: [ + LinearProgressIndicator( + value: progres, + color: sevenColor, + backgroundColor: secondaryColor, + ), + Expanded(child: WebViewWidget(controller: controller)), + ], + ), + ); + } +} diff --git a/lib/widgets/wishlist_page.dart b/lib/widgets/wishlist_page.dart new file mode 100644 index 0000000..84cc039 --- /dev/null +++ b/lib/widgets/wishlist_page.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:initial_folder/base_service.dart'; +import 'package:initial_folder/helper/validator.dart'; +import 'package:initial_folder/models/wishlist_model.dart'; +import 'package:initial_folder/screens/detail_course/detail_course_screen.dart'; +import 'package:initial_folder/screens/whislist/wishlist_card.dart'; + +class MyWishlistPage extends StatelessWidget { + const MyWishlistPage({Key? key, required this.wishlistDataModel}) + : super(key: key); + final DataWihslistModel wishlistDataModel; + + @override + Widget build(BuildContext context) { + return Container( + child: WishlistCard( + id: wishlistDataModel.wishlistId ?? '', + thumbnail: wishlistDataModel.thumbnail ?? + '$baseUrl/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', + title: wishlistDataModel.title ?? '', + numberOfRatings: wishlistDataModel.review[0].totalReview ?? '0', + press: () { + print(wishlistDataModel.courseId); + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (context) => DetailCourseScreen( + // idcourse: wishlistDataModel.courseId ?? '0', + // ), + // ), + // ); + }, + price: (wishlistDataModel.discountPrice == '0') + ? (wishlistDataModel.price == '0' + ? 'Gratis' + : numberFormat(wishlistDataModel.price)) + : numberFormat(wishlistDataModel.discountPrice), + isTopCourse: '0', + instructorName: wishlistDataModel.instructor ?? '', + rating: wishlistDataModel.review[0].avgRating != null + ? '${wishlistDataModel.review[0].avgRating}' + : '5.0', + realPrice: (wishlistDataModel.discountPrice == '0') + ? '' + : numberFormat(wishlistDataModel.price), + specificRating: double.parse(wishlistDataModel.review[0].avgRating != null + ? '${wishlistDataModel.review[0].avgRating}' + : '0') + .toString(), + courseId: wishlistDataModel.courseId.toString(), + )); + } +} +// return ProductCard( +// id: othersCourse.idCourse, +// thumbnail: othersCourse.thumbnail ?? +// 'http://api.vocasia.pasia.id/uploads/courses_thumbnail/course_thumbnail_default_57.jpg', +// title: othersCourse.title, +// instructorName: othersCourse.instructorName, +// specificRating: double.parse( +// othersCourse.rating[0]!.avgRating != null +// ? '${othersCourse.rating[0]!.avgRating}' +// : '0') +// .toString(), +// rating: othersCourse.rating[0]!.avgRating != null +// ? '${othersCourse.rating[0]!.avgRating}' +// : '5.0', +// numberOfRatings: othersCourse.rating[0]!.totalReview ?? '0', +// isTopCourse: othersCourse.topCourse!, +// price: (othersCourse.discountPrice == '0') +// ? 'Gratis' +// : numberFormat(othersCourse.discountPrice), +// realPrice: (othersCourse.price == '0') +// ? '' +// : numberFormat(othersCourse.price), +// press: () { +// print(othersCourse.idCourse); +// Navigator.push( +// context, +// MaterialPageRoute( +// builder: (context) => DetailCourseScreen( +// idcourse: othersCourse.idCourse, +// ), +// ), +// ); +// }, +// ); diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..9f0ccc1 --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "initial_folder") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.initial_folder") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..d8a4468 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,27 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include <file_saver/file_saver_plugin.h> +#include <file_selector_linux/file_selector_plugin.h> +#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h> +#include <url_launcher_linux/url_launcher_plugin.h> + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_saver_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSaverPlugin"); + file_saver_plugin_register_with_registrar(file_saver_registrar); + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); + flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include <flutter_linux/flutter_linux.h> + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..08dfac1 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,27 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_saver + file_selector_linux + flutter_secure_storage_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/main.cc b/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/my_application.cc b/linux/my_application.cc new file mode 100644 index 0000000..da5c6d6 --- /dev/null +++ b/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include <flutter_linux/flutter_linux.h> +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "initial_folder"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "initial_folder"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/my_application.h b/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include <gtk/gtk.h> + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..b73d904 --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,46 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import connectivity_plus +import facebook_auth_desktop +import file_saver +import file_selector_macos +import firebase_auth +import firebase_core +import firebase_messaging +import flutter_secure_storage_macos +import google_sign_in_ios +import package_info_plus +import path_provider_foundation +import patrol +import share_plus +import shared_preferences_foundation +import sqflite +import url_launcher_macos +import video_player_avfoundation +import wakelock_plus + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + FacebookAuthDesktopPlugin.register(with: registry.registrar(forPlugin: "FacebookAuthDesktopPlugin")) + FileSaverPlugin.register(with: registry.registrar(forPlugin: "FileSaverPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) + FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) + FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + PatrolPlugin.register(with: registry.registrar(forPlugin: "PatrolPlugin")) + SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..58ccd05 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,695 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; }; + 33CC10ED2044A3C60003C045 /* initial_folder.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "initial_folder.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = "<group>"; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = "<group>"; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = "<group>"; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* initial_folder.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = "<group>"; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = "<group>"; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = "<group>"; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = "<group>"; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* initial_folder.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/initial_folder.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/initial_folder"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/initial_folder.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/initial_folder"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/initial_folder.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/initial_folder"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IDEDidComputeMac32BitWarning</key> + <true/> +</dict> +</plist> diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..515d394 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1430" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "33CC10EC2044A3C60003C045" + BuildableName = "initial_folder.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "33CC10EC2044A3C60003C045" + BuildableName = "initial_folder.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <Testables> + <TestableReference + skipped = "NO" + parallelizable = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "331C80D4294CF70F00263BE5" + BuildableName = "RunnerTests.xctest" + BlueprintName = "RunnerTests" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "33CC10EC2044A3C60003C045" + BuildableName = "initial_folder.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + </LaunchAction> + <ProfileAction + buildConfiguration = "Profile" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "33CC10EC2044A3C60003C045" + BuildableName = "initial_folder.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "group:Runner.xcodeproj"> + </FileRef> +</Workspace> diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IDEDidComputeMac32BitWarning</key> + <true/> +</dict> +</plist> diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> + <dependencies> + <deployment identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> + <connections> + <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application" customClass="NSObject"/> + <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target"> + <connections> + <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/> + <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/> + </connections> + </customObject> + <customObject id="YLy-65-1bz" customClass="NSFontManager"/> + <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6"> + <items> + <menuItem title="APP_NAME" id="1Xt-HY-uBw"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr"> + <items> + <menuItem title="About APP_NAME" id="5kV-Vb-QxS"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/> + <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/> + <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/> + <menuItem title="Services" id="NMo-om-nkz"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/> + </menuItem> + <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/> + <menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN"> + <connections> + <action selector="hide:" target="-1" id="PnN-Uc-m68"/> + </connections> + </menuItem> + <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO"> + <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> + <connections> + <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/> + </connections> + </menuItem> + <menuItem title="Show All" id="Kd2-mp-pUS"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/> + <menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi"> + <connections> + <action selector="terminate:" target="-1" id="Te7-pn-YzF"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="Edit" id="5QF-Oa-p0T"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Edit" id="W48-6f-4Dl"> + <items> + <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg"> + <connections> + <action selector="undo:" target="-1" id="M6e-cu-g7V"/> + </connections> + </menuItem> + <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam"> + <connections> + <action selector="redo:" target="-1" id="oIA-Rs-6OD"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/> + <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG"> + <connections> + <action selector="cut:" target="-1" id="YJe-68-I9s"/> + </connections> + </menuItem> + <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU"> + <connections> + <action selector="copy:" target="-1" id="G1f-GL-Joy"/> + </connections> + </menuItem> + <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL"> + <connections> + <action selector="paste:" target="-1" id="UvS-8e-Qdg"/> + </connections> + </menuItem> + <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk"> + <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> + <connections> + <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/> + </connections> + </menuItem> + <menuItem title="Delete" id="pa3-QI-u2k"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/> + </connections> + </menuItem> + <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m"> + <connections> + <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/> + <menuItem title="Find" id="4EN-yA-p0u"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Find" id="1b7-l0-nxx"> + <items> + <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W"> + <connections> + <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/> + </connections> + </menuItem> + <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz"> + <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> + <connections> + <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/> + </connections> + </menuItem> + <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye"> + <connections> + <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/> + </connections> + </menuItem> + <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV"> + <connections> + <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/> + </connections> + </menuItem> + <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt"> + <connections> + <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/> + </connections> + </menuItem> + <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd"> + <connections> + <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Spelling" id="3IN-sU-3Bg"> + <items> + <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI"> + <connections> + <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/> + </connections> + </menuItem> + <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7"> + <connections> + <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/> + <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/> + </connections> + </menuItem> + <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/> + </connections> + </menuItem> + <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="Substitutions" id="9ic-FL-obx"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Substitutions" id="FeM-D8-WVr"> + <items> + <menuItem title="Show Substitutions" id="z6F-FW-3nz"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/> + <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/> + </connections> + </menuItem> + <menuItem title="Smart Quotes" id="hQb-2v-fYv"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/> + </connections> + </menuItem> + <menuItem title="Smart Dashes" id="rgM-f4-ycn"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/> + </connections> + </menuItem> + <menuItem title="Smart Links" id="cwL-P1-jid"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/> + </connections> + </menuItem> + <menuItem title="Data Detectors" id="tRr-pd-1PS"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/> + </connections> + </menuItem> + <menuItem title="Text Replacement" id="HFQ-gK-NFA"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="Transformations" id="2oI-Rn-ZJC"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Transformations" id="c8a-y6-VQd"> + <items> + <menuItem title="Make Upper Case" id="vmV-6d-7jI"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/> + </connections> + </menuItem> + <menuItem title="Make Lower Case" id="d9M-CD-aMd"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/> + </connections> + </menuItem> + <menuItem title="Capitalize" id="UEZ-Bs-lqG"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="Speech" id="xrE-MZ-jX0"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Speech" id="3rS-ZA-NoH"> + <items> + <menuItem title="Start Speaking" id="Ynk-f8-cLZ"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/> + </connections> + </menuItem> + <menuItem title="Stop Speaking" id="Oyz-dy-DGm"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="View" id="H8h-7b-M4v"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="View" id="HyV-fh-RgO"> + <items> + <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa"> + <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/> + <connections> + <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="Window" id="aUF-d1-5bR"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo"> + <items> + <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV"> + <connections> + <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/> + </connections> + </menuItem> + <menuItem title="Zoom" id="R4o-n2-Eq4"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/> + </connections> + </menuItem> + <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/> + <menuItem title="Bring All to Front" id="LE2-aR-0XJ"> + <modifierMask key="keyEquivalentModifierMask"/> + <connections> + <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/> + </connections> + </menuItem> + </items> + </menu> + </menuItem> + <menuItem title="Help" id="EPT-qC-fAb"> + <modifierMask key="keyEquivalentModifierMask"/> + <menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/> + </menuItem> + </items> + <point key="canvasLocation" x="142" y="-258"/> + </menu> + <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target"> + <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> + <rect key="contentRect" x="335" y="390" width="800" height="600"/> + <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/> + <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> + <rect key="frame" x="0.0" y="0.0" width="800" height="600"/> + <autoresizingMask key="autoresizingMask"/> + </view> + </window> + </objects> +</document> diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..2bdb847 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = initial_folder + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.initialFolder + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.security.app-sandbox</key> + <true/> + <key>com.apple.security.cs.allow-jit</key> + <true/> + <key>com.apple.security.network.server</key> + <true/> +</dict> +</plist> diff --git a/macos/Runner/GoogleService-Info.plist b/macos/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..3c08e1c --- /dev/null +++ b/macos/Runner/GoogleService-Info.plist @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CLIENT_ID</key> + <string>652715934272-4vdmmf2mdh5nb4j0lnutspkcceh8brdn.apps.googleusercontent.com</string> + <key>REVERSED_CLIENT_ID</key> + <string>com.googleusercontent.apps.652715934272-4vdmmf2mdh5nb4j0lnutspkcceh8brdn</string> + <key>ANDROID_CLIENT_ID</key> + <string>652715934272-8bgpnurmsj0lg8e6c9cg1ccogptm6erm.apps.googleusercontent.com</string> + <key>API_KEY</key> + <string>AIzaSyDVS6d5Y3GBQXVhk1HfL2GdiC1Wx251b_c</string> + <key>GCM_SENDER_ID</key> + <string>652715934272</string> + <key>PLIST_VERSION</key> + <string>1</string> + <key>BUNDLE_ID</key> + <string>com.example.initialFolder.RunnerTests</string> + <key>PROJECT_ID</key> + <string>vocasia-bbfb5</string> + <key>STORAGE_BUCKET</key> + <string>vocasia-bbfb5.appspot.com</string> + <key>IS_ADS_ENABLED</key> + <false></false> + <key>IS_ANALYTICS_ENABLED</key> + <false></false> + <key>IS_APPINVITE_ENABLED</key> + <true></true> + <key>IS_GCM_ENABLED</key> + <true></true> + <key>IS_SIGNIN_ENABLED</key> + <true></true> + <key>GOOGLE_APP_ID</key> + <string>1:652715934272:ios:05922d3f99967a1b3195a5</string> +</dict> +</plist> \ No newline at end of file diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>$(DEVELOPMENT_LANGUAGE)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIconFile</key> + <string></string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>$(FLUTTER_BUILD_NAME)</string> + <key>CFBundleVersion</key> + <string>$(FLUTTER_BUILD_NUMBER)</string> + <key>LSMinimumSystemVersion</key> + <string>$(MACOSX_DEPLOYMENT_TARGET)</string> + <key>NSHumanReadableCopyright</key> + <string>$(PRODUCT_COPYRIGHT)</string> + <key>NSMainNibFile</key> + <string>MainMenu</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string> +</dict> +</plist> diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.security.app-sandbox</key> + <true/> +</dict> +</plist> diff --git a/macos/RunnerTests/RunnerTests.swift b/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/macos/firebase_app_id_file.json b/macos/firebase_app_id_file.json new file mode 100644 index 0000000..27c43f0 --- /dev/null +++ b/macos/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:652715934272:ios:05922d3f99967a1b3195a5", + "FIREBASE_PROJECT_ID": "vocasia-bbfb5", + "GCM_SENDER_ID": "652715934272" +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..0cee575 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1882 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: f5628cd9c92ed11083f425fd1f8f1bc60ecdda458c81d73b143aeda036c35fe7 + url: "https://pub.dev" + source: hosted + version: "1.3.16" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "8bf17a8ff6ea17499e40a2d2542c2f481cd7615760c6d34065cb22bfd22e6880" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + archive: + dependency: transitive + description: + name: archive + sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" + url: "https://pub.dev" + source: hosted + version: "3.4.10" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + barcode: + dependency: transitive + description: + name: barcode + sha256: "91b143666f7bb13636f716b6d4e412e372ab15ff7969799af8c9e30a382e9385" + url: "https://pub.dev" + source: hosted + version: "2.2.6" + bazel_worker: + dependency: transitive + description: + name: bazel_worker + sha256: "6f306845d941808bed2fdbd7db3a39de273a8248a9303cfebf0cfa861372616e" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + bidi: + dependency: transitive + description: + name: bidi + sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63" + url: "https://pub.dev" + source: hosted + version: "2.0.10" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_modules: + dependency: transitive + description: + name: build_modules + sha256: "7a7d46498fa53b98facaa41e2048ff12cbf18eca2c94fcabea3b1760c8900308" + url: "https://pub.dev" + source: hosted + version: "5.0.6" + build_web_compilers: + dependency: "direct main" + description: + name: build_web_compilers + sha256: ebd3db74f0d53ae9b91a214016e35733add484a1a3014e188754954e8d69843c + url: "https://pub.dev" + source: hosted + version: "4.0.8" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + carousel_slider: + dependency: "direct main" + description: + name: carousel_slider + sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42" + url: "https://pub.dev" + source: hosted + version: "4.2.1" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + cherry_toast: + dependency: "direct main" + description: + name: cherry_toast + sha256: b5d7d40e4bbf0842af400da3670f55220873fd9dac40e54f1aa2c8fda7398518 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + chewie: + dependency: "direct main" + description: + name: chewie + sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144" + url: "https://pub.dev" + source: hosted + version: "1.7.5" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + connectivity_plus: + dependency: "direct main" + description: + name: connectivity_plus + sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + connectivity_plus_platform_interface: + dependency: transitive + description: + name: connectivity_plus_platform_interface + sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + url: "https://pub.dev" + source: hosted + version: "1.2.4" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + url: "https://pub.dev" + source: hosted + version: "0.3.3+8" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f" + url: "https://pub.dev" + source: hosted + version: "0.17.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + url: "https://pub.dev" + source: hosted + version: "1.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + dio: + dependency: transitive + description: + name: dio + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + easy_image_viewer: + dependency: "direct main" + description: + name: easy_image_viewer + sha256: eca828c492d580f9bd775495b281cf6481d34109b73010f1e9a3db87c4d2e5f7 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + easy_pdf_viewer: + dependency: "direct main" + description: + name: easy_pdf_viewer + sha256: "1a2e6708000952d9ada5a22f44fbcdc2b163a000369db94f89dc8d07e30a488f" + url: "https://pub.dev" + source: hosted + version: "1.0.8" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + expandable: + dependency: "direct main" + description: + name: expandable + sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + facebook_auth_desktop: + dependency: transitive + description: + name: facebook_auth_desktop + sha256: "2e652a1dec2841f6043abea0e773b9de0caadada78ef8804d57ede6d09c7a8c2" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + file_picker: + dependency: transitive + description: + name: file_picker + sha256: "1bbf65dd997458a08b531042ec3794112a6c39c07c37ff22113d2e7e4f81d4e4" + url: "https://pub.dev" + source: hosted + version: "6.2.1" + file_saver: + dependency: "direct main" + description: + name: file_saver + sha256: bdebc720e17b3e01aba59da69b6d47020a7e5ba7d5c75bd9194f9618d5f16ef4 + url: "https://pub.dev" + source: hosted + version: "0.2.12" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + url: "https://pub.dev" + source: hosted + version: "0.9.2+1" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + url: "https://pub.dev" + source: hosted + version: "0.9.3+3" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0 + url: "https://pub.dev" + source: hosted + version: "0.9.3+1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "279b2773ff61afd9763202cb5582e2b995ee57419d826b9af6517302a59b672f" + url: "https://pub.dev" + source: hosted + version: "4.16.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "3c9cfaccb7549492edf5b0c67c6dd1c6727c7830891aa6727f2fb225f0226626" + url: "https://pub.dev" + source: hosted + version: "7.0.9" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: c7b1379ccef7abf4b6816eede67a868c44142198e42350f51c01d8fc03f95a7d + url: "https://pub.dev" + source: hosted + version: "5.8.13" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb" + url: "https://pub.dev" + source: hosted + version: "2.24.2" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: c437ae5d17e6b5cc7981cf6fd458a5db4d12979905f9aafd1fea930428a9fe63 + url: "https://pub.dev" + source: hosted + version: "5.0.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0 + url: "https://pub.dev" + source: hosted + version: "2.10.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "980259425fa5e2afc03e533f33723335731d21a56fd255611083bceebf4373a8" + url: "https://pub.dev" + source: hosted + version: "14.7.10" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "54e283a0e41d81d854636ad0dad73066adc53407a60a7c3189c9656e2f1b6107" + url: "https://pub.dev" + source: hosted + version: "4.5.18" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "90dc7ed885e90a24bb0e56d661d4d2b5f84429697fd2cbb9e5890a0ca370e6f4" + url: "https://pub.dev" + source: hosted + version: "3.5.18" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + floating: + dependency: "direct main" + description: + name: floating + sha256: "4a5e37e7694f2879f757a5442d00cf3acb285264f8db774a617e13cdd4081b84" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_cache_manager: + dependency: "direct main" + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + flutter_countdown_timer: + dependency: "direct main" + description: + name: flutter_countdown_timer + sha256: dfcbd7d6f76a5589f78f3f3ba2f9ea2e199368eccc1adce4153ce985b9587bc5 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" + flutter_downloader: + dependency: "direct main" + description: + name: flutter_downloader + sha256: e130001cf85d8d7450b8318a4670c19e495dd8c102ad4b15a512e9405e7c451d + url: "https://pub.dev" + source: hosted + version: "1.11.6" + flutter_facebook_auth: + dependency: "direct main" + description: + name: flutter_facebook_auth + sha256: "23df546eeb704eecdf9e89561f7a9d78e549eaae4c103fb317a40983995a00c1" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + flutter_facebook_auth_platform_interface: + dependency: transitive + description: + name: flutter_facebook_auth_platform_interface + sha256: "86630c4dbba1c20fba26ea9e59ad0d48f5ff59e7373cacd36f916160186f9ce9" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_facebook_auth_web: + dependency: transitive + description: + name: flutter_facebook_auth_web + sha256: "22dca8091409309ad85b9f430fbd8f57b686276979da5195e7e97587352567ce" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_feather_icons: + dependency: "direct main" + description: + name: flutter_feather_icons + sha256: b33b9c276fc8108254632da6644cf01f71af6c17fbfb26e136a86945f5ff9b67 + url: "https://pub.dev" + source: hosted + version: "2.0.0+1" + flutter_html: + dependency: "direct main" + description: + name: flutter_html + sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee" + url: "https://pub.dev" + source: hosted + version: "3.0.0-beta.2" + flutter_image_compress: + dependency: "direct main" + description: + name: flutter_image_compress + sha256: "37f1b26399098e5f97b74c1483f534855e7dff68ead6ddaccf747029fb03f29f" + url: "https://pub.dev" + source: hosted + version: "1.1.3" + flutter_inappwebview: + dependency: transitive + description: + name: flutter_inappwebview + sha256: d198297060d116b94048301ee6749cd2e7d03c1f2689783f52d210a6b7aba350 + url: "https://pub.dev" + source: hosted + version: "5.8.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + url: "https://pub.dev" + source: hosted + version: "2.0.3" + flutter_native_splash: + dependency: "direct main" + description: + name: flutter_native_splash + sha256: "9cdb5d9665dab5d098dc50feab74301c2c228cd02ca25c9b546ab572cebcd6af" + url: "https://pub.dev" + source: hosted + version: "2.3.9" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + url: "https://pub.dev" + source: hosted + version: "2.0.17" + flutter_rating_bar: + dependency: "direct main" + description: + name: flutter_rating_bar + sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93 + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_secure_storage: + dependency: transitive + description: + name: flutter_secure_storage + sha256: ffdbb60130e4665d2af814a0267c481bcf522c41ae2e43caf69fa0146876d685 + url: "https://pub.dev" + source: hosted + version: "9.0.0" + flutter_secure_storage_linux: + dependency: transitive + description: + name: flutter_secure_storage_linux + sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_secure_storage_macos: + dependency: transitive + description: + name: flutter_secure_storage_macos + sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + url: "https://pub.dev" + source: hosted + version: "3.0.1" + flutter_secure_storage_platform_interface: + dependency: transitive + description: + name: flutter_secure_storage_platform_interface + sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + flutter_secure_storage_web: + dependency: transitive + description: + name: flutter_secure_storage_web + sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_secure_storage_windows: + dependency: transitive + description: + name: flutter_secure_storage_windows + sha256: "5809c66f9dd3b4b93b0a6e2e8561539405322ee767ac2f64d084e2ab5429d108" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + flutter_svg: + dependency: "direct main" + description: + name: flutter_svg + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + url: "https://pub.dev" + source: hosted + version: "2.0.10+1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_text_viewer: + dependency: "direct main" + description: + name: flutter_text_viewer + sha256: "1903d62684f67420478d7179a412808af3082ebe5d53a0050a8ebbbf44153b73" + url: "https://pub.dev" + source: hosted + version: "0.0.6" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + sha256: "52671aea66da73b58d42ec6d0912b727a42248dd9a7c76d6c20f275783c48c08" + url: "https://pub.dev" + source: hosted + version: "10.6.0" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: d0b88dc35a7f97fd91fec0cf8f165abd97a57977968d8fc02ba0bc92e14ba07e + url: "https://pub.dev" + source: hosted + version: "7.6.6" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: f0b8d115a13ecf827013ec9fc883390ccc0e87a96ed5347a3114cac177ef18e8 + url: "https://pub.dev" + source: hosted + version: "6.1.0" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "0c56c2c5d60d6dfaf9725f5ad4699f04749fb196ee5a70487a46ef184837ccf6" + url: "https://pub.dev" + source: hosted + version: "0.3.0+2" + google_sign_in: + dependency: "direct main" + description: + name: google_sign_in + sha256: "0b8787cb9c1a68ad398e8010e8c8766bfa33556d2ab97c439fb4137756d7308f" + url: "https://pub.dev" + source: hosted + version: "6.2.1" + google_sign_in_android: + dependency: transitive + description: + name: google_sign_in_android + sha256: bfd42c81c30c6faba16e0f62968d5505a87504aaa672b3155ee931461abb0a49 + url: "https://pub.dev" + source: hosted + version: "6.1.21" + google_sign_in_ios: + dependency: transitive + description: + name: google_sign_in_ios + sha256: b7d444abd3b4ef718e32d766c84b5a5d00d4e63a673075a435e6aad0e85e4d20 + url: "https://pub.dev" + source: hosted + version: "5.7.2" + google_sign_in_platform_interface: + dependency: transitive + description: + name: google_sign_in_platform_interface + sha256: "1f6e5787d7a120cc0359ddf315c92309069171306242e181c09472d1b00a2971" + url: "https://pub.dev" + source: hosted + version: "2.4.5" + google_sign_in_web: + dependency: transitive + description: + name: google_sign_in_web + sha256: a278ea2d01013faf341cbb093da880d0f2a552bbd1cb6ee90b5bebac9ba69d77 + url: "https://pub.dev" + source: hosted + version: "0.12.3+2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" + http: + dependency: "direct main" + description: + name: http + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 + url: "https://pub.dev" + source: hosted + version: "1.1.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + image: + dependency: transitive + description: + name: image + sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271" + url: "https://pub.dev" + source: hosted + version: "4.1.3" + image_gallery_saver: + dependency: "direct main" + description: + name: image_gallery_saver + sha256: "0aba74216a4d9b0561510cb968015d56b701ba1bd94aace26aacdd8ae5761816" + url: "https://pub.dev" + source: hosted + version: "2.0.3" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "1a27bf4cc0330389cebe465bab08fe6dec97e44015b4899637344bb7297759ec" + url: "https://pub.dev" + source: hosted + version: "0.8.9+2" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: eac0a62104fa12feed213596df0321f57ce5a572562f72a68c4ff81e9e4caacf + url: "https://pub.dev" + source: hosted + version: "0.8.9" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b + url: "https://pub.dev" + source: hosted + version: "2.9.3" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" + infinite_listview: + dependency: transitive + description: + name: infinite_listview + sha256: f6062c1720eb59be553dfa6b89813d3e8dd2f054538445aaa5edaddfa5195ce6 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + json_serializable: + dependency: transitive + description: + name: json_serializable + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 + url: "https://pub.dev" + source: hosted + version: "6.7.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + list_counter: + dependency: transitive + description: + name: list_counter + sha256: c447ae3dfcd1c55f0152867090e67e219d42fe6d4f2807db4bbe8b8d69912237 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + lite_rolling_switch: + dependency: "direct main" + description: + name: lite_rolling_switch + sha256: f5685911d249534ac6ac9862622f18846d31799c90fa4600b475d902d132b090 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + meta: + dependency: transitive + description: + name: meta + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + url: "https://pub.dev" + source: hosted + version: "1.10.0" + midtrans_sdk: + dependency: "direct main" + description: + name: midtrans_sdk + sha256: "2e0ce5aa20f4bce2bb8eb506ad93417d1a429e220cb4d5351ee8b1f1fd9c9100" + url: "https://pub.dev" + source: hosted + version: "0.4.0" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + nm: + dependency: transitive + description: + name: nm + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + numberpicker: + dependency: transitive + description: + name: numberpicker + sha256: "4c129154944b0f6b133e693f8749c3f8bfb67c4d07ef9dcab48b595c22d1f156" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + open_file: + dependency: "direct main" + description: + name: open_file + sha256: a5a32d44acb7c899987d0999e1e3cbb0a0f1adebbf41ac813ec6d2d8faa0af20 + url: "https://pub.dev" + source: hosted + version: "3.3.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "88bc797f44a94814f2213db1c9bd5badebafdfb8290ca9f78d4b9ee2a3db4d79" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + path: + dependency: transitive + description: + name: path + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" + source: hosted + version: "1.8.3" + path_parsing: + dependency: transitive + description: + name: path_parsing + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" + source: hosted + version: "1.0.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + patrol: + dependency: "direct dev" + description: + name: patrol + sha256: fefd5687993c4a8345666a8666f8d4c2135a8808a8767084156b4667c8634f15 + url: "https://pub.dev" + source: hosted + version: "3.6.1" + patrol_finders: + dependency: transitive + description: + name: patrol_finders + sha256: ac33527cc1b63e3aa131dbd7107cfda8ee2df0fb4a4a423c067174a2e60db77b + url: "https://pub.dev" + source: hosted + version: "2.0.2" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "93cbb2c06de9bab91844550f19896b2373e7a5ce25173995e7e5ec5e1741429d" + url: "https://pub.dev" + source: hosted + version: "3.10.7" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "860c6b871c94c78e202dc69546d4d8fd84bd59faeb36f8fb9888668a53ff4f78" + url: "https://pub.dev" + source: hosted + version: "11.1.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "2f1bec180ee2f5665c22faada971a8f024761f632e93ddc23310487df52dcfa6" + url: "https://pub.dev" + source: hosted + version: "12.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: "1a816084338ada8d574b1cb48390e6e8b19305d5120fe3a37c98825bacc78306" + url: "https://pub.dev" + source: hosted + version: "9.2.0" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "11b762a8c123dced6461933a88ea1edbbe036078c3f9f41b08886e678e7864df" + url: "https://pub.dev" + source: hosted + version: "0.1.0+2" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: d87349312f7eaf6ce0adaf668daf700ac5b06af84338bd8b8574dfbd93ffe1a1 + url: "https://pub.dev" + source: hosted + version: "4.0.2" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1e8640c1e39121128da6b816d236e714d2cf17fac5a105dd6acdd3403a628004" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: "direct main" + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointer_interceptor: + dependency: transitive + description: + name: pointer_interceptor + sha256: adf7a637f97c077041d36801b43be08559fd4322d2127b3f20bb7be1b9eebc22 + url: "https://pub.dev" + source: hosted + version: "0.9.3+7" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + protobuf: + dependency: transitive + description: + name: protobuf + sha256: "68645b24e0716782e58948f8467fd42a880f255096a821f9e7d0ec625b00c84d" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" + url: "https://pub.dev" + source: hosted + version: "6.1.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + pusher_client: + dependency: "direct main" + description: + name: pusher_client + sha256: "0dce30a1b4b0dca2a9af2f680eec495b9cdc2b0281491426f3af9718776dc14a" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + qr: + dependency: transitive + description: + name: qr + sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + qr_flutter: + dependency: "direct main" + description: + name: qr_flutter + sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + quill_html_editor: + dependency: "direct main" + description: + name: quill_html_editor + sha256: e8cc612f97a6368299c3cfa292e312efebb768c6478f075bc2bdafe2aa87831a + url: "https://pub.dev" + source: hosted + version: "2.2.7" + recase: + dependency: "direct main" + description: + name: recase + sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213 + url: "https://pub.dev" + source: hosted + version: "4.1.0" + responsive_framework: + dependency: "direct main" + description: + name: responsive_framework + sha256: "23aa7be5d5136922df708f6ddccb5ec4e0eb8bc576c3f105f5093f31d6b68e50" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + scratch_space: + dependency: transitive + description: + name: scratch_space + sha256: "8510fbff458d733a58fc427057d1ac86303b376d609d6e1bc43f240aad9aa445" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + screenshot: + dependency: "direct main" + description: + name: screenshot + sha256: "455284ff1f5b911d94a43c25e1385485cf6b4f288293eba68f15dad711c7b81c" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + share_plus: + dependency: "direct main" + description: + name: share_plus + sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd + url: "https://pub.dev" + source: hosted + version: "7.2.1" + share_plus_platform_interface: + dependency: transitive + description: + name: share_plus_platform_interface + sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956 + url: "https://pub.dev" + source: hosted + version: "3.3.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" + url: "https://pub.dev" + source: hosted + version: "2.3.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: bb4738f15b23352822f4c42a531677e5c6f522e079461fd240ead29d8d8a54a6 + url: "https://pub.dev" + source: hosted + version: "2.5.0+2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + styled_text: + dependency: "direct main" + description: + name: styled_text + sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3 + url: "https://pub.dev" + source: hosted + version: "8.1.0" + syncfusion_flutter_core: + dependency: transitive + description: + name: syncfusion_flutter_core + sha256: "8db8f55c77f56968681447d3837c10f27a9e861e238a898fda116c7531def979" + url: "https://pub.dev" + source: hosted + version: "21.2.10" + syncfusion_flutter_pdf: + dependency: "direct main" + description: + name: syncfusion_flutter_pdf + sha256: a42186922a416c2c9634a8f221aee261101babc2d30b1a1e908a7f034e743046 + url: "https://pub.dev" + source: hosted + version: "21.2.4" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + tap_debouncer: + dependency: "direct main" + description: + name: tap_debouncer + sha256: "9ec465354c1f59c4ce189b9e743161a38a0cb0f694d97db0b109282a5f146e9f" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96 + url: "https://pub.dev" + source: hosted + version: "6.2.3" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cdb7b6da34483f9b2c9f8b2b29bc468fa7271d92e2021607ca0c4d3bcb04cdd4 + url: "https://pub.dev" + source: hosted + version: "6.2.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f + url: "https://pub.dev" + source: hosted + version: "2.3.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + url: "https://pub.dev" + source: hosted + version: "2.2.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7 + url: "https://pub.dev" + source: hosted + version: "3.1.1" + uuid: + dependency: transitive + description: + name: uuid + sha256: "8c951c9cb6504b2aa6b3666e6de504032d9baec24bf4cbabd3eea9edd73d4d77" + url: "https://pub.dev" + source: hosted + version: "4.3.2" + vector_graphics: + dependency: transitive + description: + name: vector_graphics + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_codec: + dependency: transitive + description: + name: vector_graphics_codec + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_graphics_compiler: + dependency: transitive + description: + name: vector_graphics_compiler + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + url: "https://pub.dev" + source: hosted + version: "1.1.11+1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: afc65f4b8bcb2c188f64a591f84fb471f4f2e19fc607c65fd8d2f8fedb3dec23 + url: "https://pub.dev" + source: hosted + version: "2.8.3" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2" + url: "https://pub.dev" + source: hosted + version: "2.4.12" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "34beb3a07d4331a24f7e7b2f75b8e2b103289038e07e65529699a671b6a6e2cb" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + wakelock_plus: + dependency: transitive + description: + name: wakelock_plus + sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d + url: "https://pub.dev" + source: hosted + version: "1.1.4" + wakelock_plus_platform_interface: + dependency: transitive + description: + name: wakelock_plus_platform_interface + sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 + url: "https://pub.dev" + source: hosted + version: "0.3.0" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + sha256: "71e1bfaef41016c8d5954291df5e9f8c6172f1f6ff3af01b5656456ddb11f94c" + url: "https://pub.dev" + source: hosted + version: "4.4.4" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: "161af93c2abaf94ef2192bffb53a3658b2d721a3bf99b69aa1e47814ee18cc96" + url: "https://pub.dev" + source: hosted + version: "3.13.2" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "80b40ae4fb959957eef9fa8970b6c9accda9f49fc45c2b75154696a8e8996cfe" + url: "https://pub.dev" + source: hosted + version: "2.9.1" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: "02d8f3ebbc842704b2b662377b3ee11c0f8f1bbaa8eab6398262f40049819160" + url: "https://pub.dev" + source: hosted + version: "3.10.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8" + url: "https://pub.dev" + source: hosted + version: "5.2.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" + xmlstream: + dependency: transitive + description: + name: xmlstream + sha256: cfc14e3f256997897df9481ae630d94c2d85ada5187ebeb868bb1aabc2c977b4 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + youtube_player_flutter: + dependency: "direct main" + description: + name: youtube_player_flutter + sha256: "72d487e1a1b9155a2dc9d448c137380791101a0ff623723195275ac275ac6942" + url: "https://pub.dev" + source: hosted + version: "8.1.2" +sdks: + dart: ">=3.2.3 <3.5.0" + flutter: ">=3.16.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..df3d437 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,185 @@ +name: initial_folder +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ">=3.2.3 <4.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + firebase_core: ^2.24.2 + cached_network_image: ^3.3.1 + carousel_slider: ^4.2.1 + connectivity_plus: ^5.0.2 + equatable: ^2.0.5 + expandable: ^5.0.1 + firebase_auth: ^4.16.0 + firebase_messaging: ^14.7.10 + flutter_cache_manager: ^3.1.2 + flutter_countdown_timer: ^4.1.0 + flutter_dotenv: ^5.2.1 + flutter_downloader: ^1.11.6 + flutter_facebook_auth: ^6.0.3 + flutter_feather_icons: ^2.0.0+1 + flutter_html: ^3.0.0-beta.2 + flutter_image_compress: ^1.1.0 + flutter_native_splash: ^2.3.9 + flutter_rating_bar: ^4.0.1 + flutter_staggered_grid_view: ^0.7.0 + flutter_svg: ^2.0.10+1 + flutter_text_viewer: ^0.0.6 + floating: ^1.1.1+1 + font_awesome_flutter: ^10.6.0 + file_saver: ^0.2.5 + get_it: ^7.6.6 + google_fonts: ^6.1.0 + google_sign_in: ^6.2.1 + http: ^1.1.2 + image_gallery_saver: ^2.0.3 + image_picker: ^1.0.7 + intl: ^0.18.0 + lite_rolling_switch: ^1.0.1 + midtrans_sdk: ^0.4.0 + open_file: ^3.3.2 + path_provider: ^2.1.2 + pdf: ^3.10.7 + permission_handler: ^11.1.0 + provider: ^6.1.1 + recase: ^4.1.0 + responsive_framework: ^1.1.1 + rxdart: ^0.27.7 + screenshot: ^2.1.0 + share_plus: ^7.2.1 + shared_preferences: ^2.2.2 + shimmer: ^3.0.0 + styled_text: ^8.1.0 + syncfusion_flutter_pdf: ^21.2.4 + url_launcher: ^6.2.3 + webview_flutter: ^4.4.0 + youtube_player_flutter: ^8.1.2 + platform: ^3.1.4 + build_web_compilers: ^4.0.8 + pusher_client: ^2.0.0 + cherry_toast: ^1.9.1 + tap_debouncer: ^2.2.0 + chewie: ^1.7.5 + video_player: ^2.2.12 + easy_pdf_viewer: ^1.0.8 + easy_image_viewer: ^1.5.0 + quill_html_editor: ^2.2.7 + qr_flutter: ^4.1.0 + +flutter_native_splash: + color: "#f5f5f5" + image: assets\images\VOCASIA_SPLASH.png + # branding: assets/branding-production.png + color_dark: "#121212" + image_dark: assets\images\VOCASIA_SPLASH_DARK.png + # branding_dark: assets/branding-production.png + + android_12: + image: assets\images\VOCASIA_SPLASH.png + icon_background_color: "#f5f5f5" + image_dark: assets\images\VOCASIA_SPLASH_DARK.png + icon_background_color_dark: "#121212" + + android: true + ios: true + web: false + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + flutter_launcher_icons: ^0.13.1 + patrol: ^3.6.1 + + +flutter_icons: + image_path: "assets/images/vocasia_logoAPK_new.png" + android: true + ios: true + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + assets: + - assets/images/ + - assets/icons/ + - assets/icons/lms/ + - assets/icons/cart_gunakan.png + - .env + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + fonts: + - family: Montserrat + fonts: + - asset: assets/fonts/NotoSans-Regular.ttf + - family: Poppins + fonts: + - asset: assets/fonts/Poppins-ExtraLight.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..b5bfa50 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:initial_folder/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..52c8b23 --- /dev/null +++ b/web/index.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html> +<head> + <!-- + If you are serving your web app in a path other than the root, change the + href value below to reflect the base path you are serving from. + + The path provided below has to start and end with a slash "/" in order for + it to work correctly. + + For more details: + * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base + + This is a placeholder for base href that will be replaced by the value of + the `--base-href` argument provided to `flutter build`. + --> + <base href="$FLUTTER_BASE_HREF"> + + <meta charset="UTF-8"> + <meta content="IE=Edge" http-equiv="X-UA-Compatible"> + <meta name="description" content="A new Flutter project."> + + <!-- iOS meta tags & icons --> + <meta name="apple-mobile-web-app-capable" content="yes"> + <meta name="apple-mobile-web-app-status-bar-style" content="black"> + <meta name="apple-mobile-web-app-title" content="initial_folder"> + <link rel="apple-touch-icon" href="icons/Icon-192.png"> + + <!-- Favicon --> + <link rel="icon" type="image/png" href="favicon.png"/> + + <title>initial_folder + + + + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..1335f78 --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "initial_folder", + "short_name": "initial_folder", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..6f19acd --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(initial_folder LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "initial_folder") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..608175f --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,38 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + FileSaverPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSaverPlugin")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + FirebaseAuthPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..4bc953b --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + connectivity_plus + file_saver + file_selector_windows + firebase_auth + firebase_core + flutter_secure_storage_windows + permission_handler_windows + share_plus + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..2c0d5e0 --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "initial_folder" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "initial_folder" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "initial_folder.exe" "\0" + VALUE "ProductName", "initial_folder" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..1a28bac --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"initial_folder", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_