diff --git a/.metadata b/.metadata index 8d5d37ee..5f4336f9 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "a14f74ff3a1cbd521163c5f03d68113d50af93d3" + revision: "adc901062556672b4138e18a4dc62a4be8f4b3c2" channel: "stable" project_type: app @@ -13,11 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 - platform: android - create_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 - base_revision: a14f74ff3a1cbd521163c5f03d68113d50af93d3 + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: ios + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: linux + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: macos + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: web + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + - platform: windows + create_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 + base_revision: adc901062556672b4138e18a4dc62a4be8f4b3c2 # User provided section diff --git a/android/app/build.gradle b/android/app/build.gradle index 5dfbaf38..92f16832 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -20,12 +20,12 @@ if (project.hasProperty('google-services.json')) { def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = "7" + flutterVersionCode = "14" } def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = "1.1.0" + flutterVersionName = "1.2.6" } def keystorePropertiesFile = rootProject.file("key.properties") @@ -35,15 +35,15 @@ keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) android { namespace = "ae.gov.fcsc.stats" compileSdk = flutter.compileSdkVersion - // ndkVersion = flutter.ndkVersion - ndkVersion = "25.1.8937393" + ndkVersion = flutter.ndkVersion +// ndkVersion = "25.1.8937393" compileOptions { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 } kotlinOptions { - jvmTarget = "17" + jvmTarget = "21" } defaultConfig { diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 00000000..a1dad0ab --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "ae.gov.fcsc.uae_stat" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "ae.gov.fcsc.uae_stat" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 3e68b2f7..8d914b2a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -33,17 +33,22 @@ android:value="true"/> + + + + + + + + + - - - - + , diff --git a/android/app/src/main/kotlin/ae/gov/fcsc/uae_stat/MainActivity.kt b/android/app/src/main/kotlin/ae/gov/fcsc/uae_stat/MainActivity.kt new file mode 100644 index 00000000..23f3ea36 --- /dev/null +++ b/android/app/src/main/kotlin/ae/gov/fcsc/uae_stat/MainActivity.kt @@ -0,0 +1,5 @@ +package ae.gov.fcsc.uae_stat + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() 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 00000000..f74085f3 --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + 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 00000000..304732f8 --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index 81f8ba72..63610638 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -4,7 +4,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.6.0' // Update this if needed + classpath 'com.android.tools.build:gradle:8.7.1' // Update this if needed classpath 'com.google.gms:google-services:4.3.10' // ✅ Required for Firebase } } diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 00000000..dbee657b --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/android/gradle.properties b/android/gradle.properties index 476c2b1c..3f13b436 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,13 @@ -org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError -android.useAndroidX=true -android.enableJetifier=true +# Enable 16KB memory page support +android.bundle.enableNativeCodeCompression=false +android.experimental.enable16kPages=true org.gradleorg.gradle.java.home=/usr/lib/jvm/openlogic-openjdk-17.0.13+11-linux-x64 +# Ensure compatibility +org.gradle.jvmargs=-Xmx4G -XX:+UseParallelGC -XX:MaxMetaspaceSize=1G +org.gradle.caching=true +org.gradle.daemon=true +org.gradle.parallel=true + +android.useAndroidX=true +android.enableJetifier=false +kotlin.code.style=official \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 5d6560a4..5e022813 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip + diff --git a/android/settings.gradle b/android/settings.gradle index a2ff7785..2d7bd528 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.2.1" apply false - id "org.jetbrains.kotlin.android" version "1.9.24" apply false + id "com.android.application" version "8.6.0" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false id("com.google.gms.google-services") version "4.4.2" apply false } diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 00000000..fb605bc8 --- /dev/null +++ b/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.9.1" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 868ef877..7b484c4a 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - app_links (0.0.2): + - app_links (6.4.1): - Flutter - app_settings (5.1.1): - Flutter @@ -13,47 +13,49 @@ PODS: - GoogleUtilities/Environment (~> 8.0) - GoogleUtilities/UserDefaults (~> 8.0) - PromisesObjC (~> 2.4) - - appsflyer_sdk (6.16.2): - - AppsFlyerFramework (= 6.16.2) + - appsflyer_sdk (6.17.7): + - appsflyer_sdk/Core (= 6.17.7) + - appsflyer_sdk/Core (6.17.7): + - AppsFlyerFramework (= 6.17.7) - Flutter - - AppsFlyerFramework (6.16.2): - - AppsFlyerFramework/Main (= 6.16.2) - - AppsFlyerFramework/Main (6.16.2) + - AppsFlyerFramework (6.17.7): + - AppsFlyerFramework/Main (= 6.17.7) + - AppsFlyerFramework/Main (6.17.7) - connectivity_plus (0.0.1): - Flutter - device_info_plus (0.0.1): - Flutter - - Firebase/CoreOnly (11.10.0): - - FirebaseCore (~> 11.10.0) - - Firebase/Messaging (11.10.0): + - Firebase/CoreOnly (11.15.0): + - FirebaseCore (~> 11.15.0) + - Firebase/Messaging (11.15.0): - Firebase/CoreOnly - - FirebaseMessaging (~> 11.10.0) - - firebase_core (3.13.0): - - Firebase/CoreOnly (= 11.10.0) + - FirebaseMessaging (~> 11.15.0) + - firebase_core (3.15.2): + - Firebase/CoreOnly (= 11.15.0) - Flutter - - firebase_messaging (15.2.5): - - Firebase/Messaging (= 11.10.0) + - firebase_messaging (15.2.10): + - Firebase/Messaging (= 11.15.0) - firebase_core - Flutter - - FirebaseCore (11.10.0): - - FirebaseCoreInternal (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Logger (~> 8.0) - - FirebaseCoreInternal (11.10.0): - - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseInstallations (11.10.0): - - FirebaseCore (~> 11.10.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) + - FirebaseCore (11.15.0): + - FirebaseCoreInternal (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreInternal (11.15.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FirebaseInstallations (11.15.0): + - FirebaseCore (~> 11.15.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) - PromisesObjC (~> 2.4) - - FirebaseMessaging (11.10.0): - - FirebaseCore (~> 11.10.0) + - FirebaseMessaging (11.15.0): + - FirebaseCore (~> 11.15.0) - FirebaseInstallations (~> 11.0) - GoogleDataTransport (~> 10.0) - - GoogleUtilities/AppDelegateSwizzler (~> 8.0) - - GoogleUtilities/Environment (~> 8.0) - - GoogleUtilities/Reachability (~> 8.0) - - GoogleUtilities/UserDefaults (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Reachability (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) - nanopb (~> 3.30910.0) - Flutter (1.0.0) - flutter_native_splash (2.4.3): @@ -63,8 +65,6 @@ PODS: - FlutterMacOS - flutter_uae_pass (0.0.1): - Flutter - - flutter_web_auth_2 (3.0.0): - - Flutter - fluttertoast (0.0.2): - Flutter - google_sign_in_ios (0.0.1): @@ -135,9 +135,6 @@ PODS: - video_player_avfoundation (0.0.1): - Flutter - FlutterMacOS - - webview_flutter_wkwebview (0.0.1): - - Flutter - - FlutterMacOS DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) @@ -151,7 +148,6 @@ DEPENDENCIES: - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) - flutter_uae_pass (from `.symlinks/plugins/flutter_uae_pass/ios`) - - flutter_web_auth_2 (from `.symlinks/plugins/flutter_web_auth_2/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) @@ -161,7 +157,6 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: @@ -204,8 +199,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" flutter_uae_pass: :path: ".symlinks/plugins/flutter_uae_pass/ios" - flutter_web_auth_2: - :path: ".symlinks/plugins/flutter_web_auth_2/ios" fluttertoast: :path: ".symlinks/plugins/fluttertoast/ios" google_sign_in_ios: @@ -224,47 +217,43 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" video_player_avfoundation: :path: ".symlinks/plugins/video_player_avfoundation/darwin" - webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - app_links: 76b66b60cc809390ca1ad69bfd66b998d2387ac7 + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2 AppAuth: 1c1a8afa7e12f2ec3a294d9882dfa5ab7d3cb063 AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f - appsflyer_sdk: ebe94dc3f00b07bdd001251dbc87879bda3c494a - AppsFlyerFramework: fe5303bffcdfd941d5f570c2d21eaaea982e7bdc + appsflyer_sdk: 01d0cd834fe2fffd7f2372ab38690f7483587720 + AppsFlyerFramework: ae7caf1688348f2d7a742037bd4fc6966d39d362 connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe - Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2 - firebase_core: 2d4534e7b489907dcede540c835b48981d890943 - firebase_messaging: 75bc93a4df25faccad67f6662ae872ac9ae69b64 - FirebaseCore: 8344daef5e2661eb004b177488d6f9f0f24251b7 - FirebaseCoreInternal: ef4505d2afb1d0ebbc33162cb3795382904b5679 - FirebaseInstallations: 9980995bdd06ec8081dfb6ab364162bdd64245c3 - FirebaseMessaging: 2b9f56aa4ed286e1f0ce2ee1d413aabb8f9f5cb9 + Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e + firebase_core: 995454a784ff288be5689b796deb9e9fa3601818 + firebase_messaging: f4a41dd102ac18b840eba3f39d67e77922d3f707 + FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e + FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4 + FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843 + FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_native_splash: 6cad9122ea0fad137d23137dd14b937f3e90b145 flutter_secure_storage_darwin: ce237a8775b39723566dc72571190a3769d70468 - flutter_uae_pass: 49160e01023a9815164b94a07b6f6ce3c79b1e56 - flutter_web_auth_2: 5c8d9dcd7848b5a9efb086d24e7a9adcae979c80 + flutter_uae_pass: b643f2272a11abd3d1167f1760ad0adf35b31979 fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 - google_sign_in_ios: 205742c688aea0e64db9da03c33121694a365109 + google_sign_in_ios: 00dfa94252eb10278b64828c81bcb7158a81a53a GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleSignIn: c7f09cfbc85a1abf69187be091997c317cc33b77 GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMAppAuth: 217a876b249c3c585a54fd6f73e6b58c4f5c4238 GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 - image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 - url_launcher_ios: 694010445543906933d732453a59da0a173ae33d - video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b - webview_flutter_wkwebview: 8ebf4fded22593026f7dbff1fbff31ea98573c8d + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a PODFILE CHECKSUM: 1959d098c91d8a792531a723c4a9d7e9f6a01e38 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..17462efd --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,774 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0158C53D8729FF3CAEDD63E1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D8321227CAF760684DE6073D /* Pods_RunnerTests.framework */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 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 */; }; + A77EABEF04180C888228A6B6 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8FCB8E98366ECE496F9C6593 /* Pods_Runner.framework */; }; +/* 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 = ""; }; + 190F5FA1FAFE050ABAB22E9B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 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; }; + 36245650E9027DB0317FE314 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 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 = ""; }; + 7692EB83BCA20EB7DB6148D9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 78330B354D57D0C89C5DB6C4 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8FCB8E98366ECE496F9C6593 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 938AA2808E77FAEF6A979134 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 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 = ""; }; + CC3BC78662DC6B354B36AB2C /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + D8321227CAF760684DE6073D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A77EABEF04180C888228A6B6 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + B24AA7490A694194BE125B19 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0158C53D8729FF3CAEDD63E1 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 128B0E4DC68FEAA53D10ACCE /* Pods */ = { + isa = PBXGroup; + children = ( + 36245650E9027DB0317FE314 /* Pods-Runner.debug.xcconfig */, + 78330B354D57D0C89C5DB6C4 /* Pods-Runner.release.xcconfig */, + 7692EB83BCA20EB7DB6148D9 /* Pods-Runner.profile.xcconfig */, + 938AA2808E77FAEF6A979134 /* Pods-RunnerTests.debug.xcconfig */, + 190F5FA1FAFE050ABAB22E9B /* Pods-RunnerTests.release.xcconfig */, + CC3BC78662DC6B354B36AB2C /* Pods-RunnerTests.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 128B0E4DC68FEAA53D10ACCE /* Pods */, + C2DC67BE57D93B775E1BD572 /* Frameworks */, + ); + 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 = ""; + }; + C2DC67BE57D93B775E1BD572 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 8FCB8E98366ECE496F9C6593 /* Pods_Runner.framework */, + D8321227CAF760684DE6073D /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 73A2074E472182151AB9ED7A /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + B24AA7490A694194BE125B19 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 93BC14D0999CE33FAEA7EEE6 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 544AE9F477D1ED5A9363807B /* [CP] Embed Pods Frameworks */, + 4FA0629056C68E3C8427B28F /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + 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"; + }; + 4FA0629056C68E3C8427B28F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 544AE9F477D1ED5A9363807B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 73A2074E472182151AB9ED7A /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 93BC14D0999CE33FAEA7EEE6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 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; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 9N5SRFUGK2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FCSC; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2.5; + PRODUCT_BUNDLE_IDENTIFIER = ae.gov.fcsc.frontend.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 938AA2808E77FAEF6A979134 /* 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 = ae.gov.fcsc.uaeStat.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 = 190F5FA1FAFE050ABAB22E9B /* 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 = ae.gov.fcsc.uaeStat.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 = CC3BC78662DC6B354B36AB2C /* 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 = ae.gov.fcsc.uaeStat.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 9N5SRFUGK2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FCSC; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2.5; + PRODUCT_BUNDLE_IDENTIFIER = ae.gov.fcsc.frontend.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 9N5SRFUGK2; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = FCSC; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.2.5; + PRODUCT_BUNDLE_IDENTIFIER = ae.gov.fcsc.frontend.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + 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 00000000..919434a6 --- /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 00000000..18d98100 --- /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 00000000..f9b0d7c5 --- /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 00000000..e3773d42 --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /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 00000000..f9b0d7c5 --- /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 00000000..62666446 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index c0d807cb..d12d8318 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -22,29 +22,22 @@ $(FLUTTER_BUILD_NAME) CFBundleSignature ???? - LSApplicationQueriesSchemes - - uaepass - uaepassqa - uaepassdev - uaepassstg - CFBundleURLTypes - - CFBundleURLSchemes - - fcscstats - - - - CFBundleURLName - ae.gov.fcsc.frontend.ios - CFBundleURLSchemes - - ae.gov.fcsc.frontend.ios - - + + CFBundleURLSchemes + + fcscstats + + + + CFBundleURLName + ae.gov.fcsc.frontend.ios + CFBundleURLSchemes + + ae.gov.fcsc.frontend.ios + + CFBundleVersion $(FLUTTER_BUILD_NUMBER) @@ -56,6 +49,13 @@ ITSAppUsesNonExemptEncryption + LSApplicationQueriesSchemes + + uaepass + uaepassqa + uaepassdev + uaepassstg + LSRequiresIPhoneOS NSAppTransportSecurity @@ -84,6 +84,8 @@ Main UIStatusBarHidden + UIStatusBarStyle + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index 9b59b19b..3b2d1940 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -13,6 +13,7 @@ import 'package:uae_stat/presentation/Screens/auth_verification/registration.dar import 'package:uae_stat/presentation/Screens/auth_verification/terms&conditions.dart'; import 'package:uae_stat/presentation/Screens/charts/screens/chart_screen.dart'; import 'package:uae_stat/presentation/Screens/online_offline_verification/internet_check.dart'; +import 'package:uae_stat/presentation/routes/auth_routes/checkingUAEPass.dart'; import 'package:uae_stat/presentation/routes/auth_routes/fcsc_profile_linking.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/Competitiveness/Detailedcompetitiveness.dart'; import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/Competitiveness/competitiveness.dart'; @@ -132,6 +133,10 @@ final GoRouter router = GoRouter( path: '/myhomepage', builder: (context, state) => MyHomePage(), ), + GoRoute( + path: '/checkingUAEPass', + builder: (context, state) => checkingUAEPass(), + ), // GoRoute( // path: '/DemoHome/:dataSets', @@ -406,10 +411,24 @@ final GoRouter router = GoRouter( ), ], redirect: (context, state) async { + // If UAE PASS redirected here with ?code= + final uri = state.uri; + + // UAE PASS returned here with /login?code=xxxx + if (uri.path == '/login' && uri.queryParameters.containsKey('code')) { + final code = uri.queryParameters['code']!; + print("✨ UAE PASS Authorization Code Received: $code"); + + // Send user to /login with new param (so UI can read it) + return '/login?uaeCode=$code'; + } + if (state.uri.path == '/SessionCheckScreen' || state.uri.path == '/login' || + state.uri.path == '/checkingUAEPass' || state.uri.path == '/register' || state.uri.path.startsWith('/forgot-password/') || + state.uri.path.startsWith('/forgot-password/') || state.uri.path == '/termsandconditions' || state.uri.path == '/privacy_policy' || state.uri.path == '/fcscprofilelinking') { diff --git a/lib/presentation/Screens/auth_verification/registration.dart b/lib/presentation/Screens/auth_verification/registration.dart index 85297e17..9c5f81a0 100644 --- a/lib/presentation/Screens/auth_verification/registration.dart +++ b/lib/presentation/Screens/auth_verification/registration.dart @@ -1569,19 +1569,19 @@ class _RegisterScreenState extends ConsumerState { child: SizedBox( width: screenwidth / 1, child: ElevatedButton( - // onPressed: isRegistering - // ? null - // : _registerUser? - onPressed: () { - if (isRegistering) { - _registerUser; - } else if (cameFromLinking) { - oAuthOIDCLogin(context, ref, - 'oidc'); //Open ID connect - } else { - null; - } - }, + onPressed: isRegistering + ? null + : _registerUser, + // onPressed: () { + // if (isRegistering) { + // _registerUser; + // } else if (cameFromLinking) { + // oAuthOIDCLogin(context, ref, + // 'oidc'); //Open ID connect + // } else { + // null; + // } + // }, style: ElevatedButton.styleFrom( backgroundColor: Color( 0xFFB68A34), // Brownish color for Register diff --git a/lib/presentation/routes/auth_routes/OAthuLogin.dart b/lib/presentation/routes/auth_routes/OAthuLogin.dart new file mode 100644 index 00000000..2dc1620f --- /dev/null +++ b/lib/presentation/routes/auth_routes/OAthuLogin.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:go_router/go_router.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:pocketbase/pocketbase.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/// =============================== +/// CONFIG +/// =============================== +const String pocketBaseUrl = 'https://pocket.fcsc.gov.ae'; // 🔁 change if needed + +/// =============================== +/// LOGIN PAGE +/// =============================== +class LoginOAuthPage extends HookConsumerWidget + with WidgetsBindingObserver { + LoginOAuthPage({super.key}); + + final PocketBase pb = PocketBase(pocketBaseUrl); + + /// =============================== + /// OAuth Auto-Close (App Resume) + /// =============================== + void _initOAuthAutoClose(BuildContext context) { + WidgetsBinding.instance.addObserver( + _OAuthLifecycle( + pb: pb, + context: context, + ), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + /// Init lifecycle listener once + useEffect(() { + _initOAuthAutoClose(context); + return null; + }, const []); + + return Scaffold( + appBar: AppBar( + title: const Text('Login'), + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + /// GOOGLE LOGIN + SizedBox( + width: double.infinity, + child: OutlinedButton( + onPressed: () async { + await _startOAuth('google'); + }, + child: const Text('Sign in with Google'), + ), + ), + + const SizedBox(height: 16), + + /// APPLE LOGIN + SizedBox( + width: double.infinity, + child: OutlinedButton( + onPressed: () async { + await _startOAuth('apple'); + }, + child: const Text('Sign in with Apple'), + ), + ), + ], + ), + ), + ); + } + + /// =============================== + /// Start OAuth (NON-BLOCKING) + /// =============================== + Future _startOAuth(String provider) async { + try { + final methods = await pb.collection('users').listAuthMethods(); + + final authProvider = methods.authProviders.firstWhere( + (p) => p.name == provider, + ); + + await launchUrl( + Uri.parse(authProvider.authUrl), + mode: LaunchMode.externalApplication, + ); + } catch (e) { + debugPrint('❌ OAuth launch error: $e'); + } + } +} + +/// =============================== +/// Lifecycle Observer +/// =============================== +class _OAuthLifecycle with WidgetsBindingObserver { + final PocketBase pb; + final BuildContext context; + + _OAuthLifecycle({ + required this.pb, + required this.context, + }); + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _handleResume(); + } + } + + void _handleResume() { + if (pb.authStore.isValid) { + debugPrint('✅ OAuth login detected'); + + final user = pb.authStore.model; + debugPrint('User ID: ${user?.id}'); + debugPrint('Email: ${user?.data['email']}'); + + if (context.mounted) { + context.go('/myhomepage'); // 🔁 change route if needed + } + } + } +} diff --git a/lib/presentation/routes/auth_routes/checkingUAEPass.dart b/lib/presentation/routes/auth_routes/checkingUAEPass.dart new file mode 100644 index 00000000..7a5c66a3 --- /dev/null +++ b/lib/presentation/routes/auth_routes/checkingUAEPass.dart @@ -0,0 +1,112 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_uae_pass/flutter_uae_pass.dart'; + +class checkingUAEPass extends StatefulWidget { + const checkingUAEPass({super.key}); + + @override + State createState() => _checkingUAEPassState(); +} + +class _checkingUAEPassState extends State { + String? authCode; + String? accessToken; + ProfileData? profileData; + + final _uaePass = UaePass(); + + @override + void initState() { + super.initState(); + // _uaePass.setUpSandbox(); // This uses staging - correct for your staging app + _uaePass.setUpEnvironment( + clientId: "sandbox_stage", // from UAE PASS console + clientSecret: "sandbox_stage", // from UAE PASS console + isProduction: false, // staging environment + urlScheme: "fcscUaeStats", // 👈 this must match the scheme below + redirectUri: "fcscUaeStats://callback", // 👈 must match manifest + UAE PASS + scope: "urn:uae:digitalid:profile:general", + language: "en", + ); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('UAE Pass'), + centerTitle: true, + ), + body: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + MaterialButton( + onPressed: () => login(), + child: const Text('Sign in with UAE Pass'), + ), + const SizedBox(height: 100), + if (authCode != null) + ListTile( + title: const Text('Auth Code'), + subtitle: Text('${authCode?.substring(0, 6)}............'), + ), + if (accessToken != null) + ListTile( + title: const Text('Access Token'), + subtitle: Text('${accessToken?.substring(0, 6)}............'), + ), + if (profileData != null) + Column( + children: [ + ListTile( + title: const Text('Emirates Id'), + subtitle: Text(profileData?.idn ?? ""), + ), + ListTile( + title: const Text('Full Name EN'), + subtitle: Text(profileData?.fullnameEN ?? ""), + ), + ListTile( + title: const Text('Full Name AR'), + subtitle: Text(profileData?.fullnameAR ?? ""), + ), + ListTile( + title: const Text('Mobile'), + subtitle: Text(profileData?.mobile ?? ""), + ), + ListTile( + title: const Text('Nationality EN'), + subtitle: Text(profileData?.nationalityEN ?? ""), + ), + ], + ), + ], + ), + ), + ), + ); + } + + Future login() async { + authCode = null; + accessToken = null; + profileData = null; + setState(() {}); + try { + authCode = await _uaePass.signIn(); + accessToken = await _uaePass.getAccessToken(authCode ?? ""); + profileData = await _uaePass.getProfile(accessToken ?? ""); + + setState(() {}); + } catch (e) { + if (kDebugMode) { + print(e.toString()); + } + } + setState(() {}); + } +} \ No newline at end of file diff --git a/lib/presentation/routes/auth_routes/flutter_inappwebview.dart b/lib/presentation/routes/auth_routes/flutter_inappwebview.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/presentation/routes/auth_routes/login_route.dart b/lib/presentation/routes/auth_routes/login_route.dart index 18ee27aa..4e1c1b41 100644 --- a/lib/presentation/routes/auth_routes/login_route.dart +++ b/lib/presentation/routes/auth_routes/login_route.dart @@ -46,7 +46,7 @@ import 'package:uae_stat/config/api_config.dart'; import '../../components/indicators/locale_provider.dart'; import '../../components/my_toggle.dart'; -class LoginRoute extends HookConsumerWidget { +class LoginRoute extends HookConsumerWidget with WidgetsBindingObserver{ final String? fromPage; final Map? userInfo; @@ -438,33 +438,46 @@ class LoginRoute extends HookConsumerWidget { } Future login(BuildContext context, ref) async { - final email = profileData?.email ?? 'surendarsuri3450@gmail.com'; - final uuid = profileData?.uuid ?? 'eac45287-5e1b-4251-b338-6aa7f49464a0'; - await checkUserWithUhid( - email: email ?? '', - uuid: uuid ?? '', - context: context, - userInfo: profileData, - ); + // final email = profileData?.email ?? 'surendarsuri3450@gmail.com'; + // final uuid = profileData?.uuid ?? 'eac45287-5e1b-4251-b338-6aa7f49464a0'; + // await checkUserWithUhid( + // email: email ?? '', + // uuid: uuid ?? '', + // context: context, + // userInfo: profileData, + // ); authCode = null; accessToken = null; profileData = null; try { authCode = await _uaePass.signIn(); print('authCode $authCode'); - accessToken = await _uaePass.getAccessToken(authCode ?? ""); - print('accessToken $accessToken'); - profileData = await _uaePass.getProfile(accessToken ?? ""); - print('profileData $profileData'); - final email = profileData?.email ?? 'surendarsuri30@gmail.com'; - final uuid = profileData?.uuid ?? 'eac45287-5e1b-4251-b338-6aa7f49464a0'; - - await checkUserWithUhid( - email: email ?? '', - uuid: uuid ?? '', + showDialog( context: context, - userInfo: profileData, + builder: (context) => AlertDialog( + title: const Text('Auth Code'), + content: Text('authCode: $authCode'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), ); + // accessToken = await _uaePass.getAccessToken(authCode ?? ""); + // print('accessToken $accessToken'); + // profileData = await _uaePass.getProfile(accessToken ?? ""); + // print('profileData $profileData'); + // final email = profileData?.email ?? ''; + // final uuid = profileData?.uuid ?? ''; + + // await checkUserWithUhid( + // email: email ?? '', + // uuid: uuid ?? '', + // context: context, + // userInfo: profileData, + // ); } catch (e) { if (kDebugMode) { print(e.toString()); @@ -490,27 +503,26 @@ class LoginRoute extends HookConsumerWidget { print('platform $platform'); try { print('platform $platform'); + // final methods = await pb.collection('users').listAuthMethods(); + // print(methods); + // final authProvider = methods.authProviders.firstWhere((p) => p.name == platform); + // print(authProvider); + // await launchUrl( + // Uri.parse(authProvider.authUrl), + // mode: LaunchMode.externalApplication, + // ); final authData = await pb.collection('users').authWithOAuth2(platform, (url) async { await launchUrl(url); }); - // final authData = await pb.collection('users').authWithOAuth2( - // platform, // 'google' or 'apple' - // (providerUrl) async { - // final Uri uri = Uri.parse(providerUrl.toString()); - // - // // Inject redirect_url so PocketBase will redirect to the app when done - // final newUri = uri.replace(queryParameters: { - // ...uri.queryParameters, - // 'redirect_url': deepLinkRedirect, - // }); - // - // // Open in external browser (ensures OS handles returning to app) - // if (!await launchUrl(newUri, mode: LaunchMode.externalApplication)) { - // throw 'Could not launch OAuth URL'; - // } - // }, - // ); + // final authData = + // await pb.collection('users').authWithOAuth2(platform, (url) async { + // await launchUrl( + // Uri.parse(url.toString()), + // mode: LaunchMode.platformDefault, + // ); + // }); + print('authData $authData'); // ✅ Check if authData is valid if (authData.token.isNotEmpty && authData.record != null) { @@ -771,13 +783,15 @@ class LoginRoute extends HookConsumerWidget { // } // } - Future checkUserWithUhid1({ - required String email, - required String uuid, + Future checkUserWithUhid({ + required String code, + required ref, + // required String email, + // required String uuid, required BuildContext context, - required userInfo, + // required userInfo, }) async { - final url = Uri.parse('$apiUrl/api/checkUserWithUhid'); + final url = Uri.parse('${apiUrl}api/checkUserWithUhid'); final headers = { 'Content-Type': 'application/json', @@ -785,38 +799,135 @@ class LoginRoute extends HookConsumerWidget { }; final body = jsonEncode({ - 'email': email, - 'uuid': uuid, + // 'email': email, + // 'uuid': uuid, + 'code' : code }); try { final response = await http.post(url, headers: headers, body: body); if (response.statusCode == 200) { - final data = jsonDecode(response.body); - print('✅ Success: $data'); - // final Map userInfoDetails = userInfo; - final Map userInfoDetails = { - "sub": "eac45287-5e1b-4251-b338-6aa7f49464a0", - "fullnameAR": "سوريندر سوري", - "gender": "Male", - "spuuid": "ASdfsfdsgfg-4672n-4284jjbjvbckjsx", - "mobile": "918056103381", - "lastnameEN": "suri", - "fullnameEN": "surendar suri", - "uuid": "eac45287-5e1b-4251-b338-6aa7f49464a0", - "lastnameAR": "سوري", - "idn": "784000000009632", - "nationalityEN": "ARE", - "firstnameEN": "surendar", - "userType": "SOP2", - "nationalityAR": "اماراتى", - "firstnameAR": "سوريندر", - "email": "surendarsuri30@gmail.com" - }; + // final data = jsonDecode(response.body); + final apiParam = { + "data": { + "avatar": "", + "collectionId": "_pb_users_auth_", + "collectionName": "users", + "country_region": "", + "created": "2025-09-27 04:39:48.639Z", + "device_token": "", + "dob": "", + "emailVisibility": false, + "full_name": "", + "id": "iwxvax2m3ra8vry", + "is_oauth_login": 0, + "is_profile_completed": false, + "language": "", + "login_count": 4, + "pushed_notification": null, + "pw_reset_token": "", + "pw_reset_token_expiry": "", + "reviewed": false, + "role": "", + "status": "", + "theme": "", + "uname": "", + "updated": "2025-11-25 09:12:01.746Z", + "user_mail_verify": false, + "username": "users45431", + "verified": true + }, + "expiresOn": 1765271521, + "message": "User found by email and UUID updated", + "status": "success", + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE3NjUyNzE1MjEsImlkIjoiaXd4dmF4Mm0zcmE4dnJ5IiwidHlwZSI6ImF1dGhSZWNvcmQifQ.jSXeGByFWsPTv70nW2YvahZi8fYCzfibF8nWqhKYPA0" - GoRouter.of(context).go('/fcscprofilelinking', extra: userInfoDetails); + }; + print('✅ Success: $apiParam'); + if (apiParam['token'] != null && + apiParam['token'].toString().isNotEmpty && + apiParam['status'] == 'success') { + + print("✔ Token exists & login success!"); + + // final metaDetails = authData.meta; + final userDetails = apiParam['data'] as Map; + final token = apiParam['token']; + final userId = userDetails['id']; + final email = userDetails['email'] ?? ''; + final uname = userDetails['uname'] ?? ''; + final isProfileComplete = userDetails['is_profile_completed'] ?? false; + final loginCount = userDetails['login_count'] ?? 0; + // final expiresOn = apiParam['expiresOn']; + + print("User ID: ${userId}"); + print("Email: ${email}"); + print("Email: ${isProfileComplete}"); + print("Email: ${loginCount}"); + + final session = await context.loaderWithErrorDialog( + () => ref + .read( + authUseCaseProvider.notifier, + ) + .googleAndAppleAuthencation(apiParam), + errorDialogBuilder: ( + error, [ + StackTrace? stackTrace, + ]) { + if (error == LoginError.invalidEmailPw) { + return context.simpleDialog( + title: context.translate( + 'Incorrect Credentials', + 'أوراق غير صحيحة', + ), + content: context.translate( + 'Your email or password is invalid. Please try again.', + 'بريدك الإلكتروني أو كلمة المرور غير صالحة. حاول مرة اخرى.', + ), + ); + } + return context.simpleDialog(); + }, + ); + + print("sessionTST - $session"); + + await loginCountApi(userId!); + await updateOAuthUserDetails(userId!, uname, ref, token); + // await saveUserId(userId!, loginCount); + await getDeviceToken(context, userId, ref, token); + await initializeTheme(context, ref, userId); + + // final se = _sessionEntityFromPocketbaseRecord(authData); + // await _LocalAuthRepo.storeLocally(se); + + // Extract intendedPath from query parameters (if coming from redirect) + final String? intendedPath = + GoRouterState.of(context).uri.queryParameters['intendedPath']; + print("Extracted intendedPath after login: $intendedPath"); + + if (isProfileComplete) { + print('Redirecting to Home / Intended Path'); + // **Set Default Locale in Provider** + if (preferredLanguage == 'ar') { + ref.read(localeProvider.notifier).setLocale(const Locale('ar')); + } else { + ref.read(localeProvider.notifier).setLocale(const Locale('en')); + } + + context.go(intendedPath ?? '/myhomepage'); + } else { + print('Redirecting to Profile Setup'); + + context.go('/profile/$userId'); + } + } else { + print("❌ Login failed: Invalid authData"); + } } else { + GoRouter.of(context).go('/fcscprofilelinking', extra: userInfo); print('❌ Error: ${response.statusCode} - ${response.body}'); } } catch (e) { @@ -824,17 +935,166 @@ class LoginRoute extends HookConsumerWidget { } } - Future checkUserWithUhid({ - required String email, - required String uuid, - required BuildContext context, - required userInfo, - }) async { - print('checkUserWithUhid'); + String buildUaepassAuthorizationUrl() { + const String authorizeEndpoint = "https://stg-id.uaepass.ae/idshub/authorize"; + const String clientId = "sandbox_stage"; + const String scope = "urn:uae:digitalid:profile:general"; + const String redirectUri = "http://localhost:56215/#/login"; + const String acrValues = "urn:safelayer:tws:policies:authentication:level:low"; - signInWithUAEPASS(); + // Generate random state (recommended) + final String state = DateTime.now().millisecondsSinceEpoch.toString(); + + final Uri uri = Uri.parse(authorizeEndpoint).replace( + queryParameters: { + "response_type": "code", + "client_id": clientId, + "scope": scope, + "redirect_uri": redirectUri, + "state": state, + "acr_values": acrValues, + }, + ); + print('UAEPASS URL : $uri'); + return uri.toString(); } + Future openUaepassLogin(BuildContext context) async { + final authUrl = buildUaepassAuthorizationUrl(); // Now this exists + + print('UAE Pass :$authUrl'); + + final uri = Uri.parse(authUrl); + + if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) { + throw 'Could not launch UAE PASS URL'; + } + + // Navigator.push( + // context, + // MaterialPageRoute( + // builder: (_) => Scaffold( + // appBar: AppBar(title: const Text("UAE PASS Login")), + // body: InAppWebView( + // initialUrlRequest: URLRequest( + // url: WebUri.uri(Uri.parse(authUrl)), + // ), + // initialOptions: InAppWebViewGroupOptions( + // crossPlatform: InAppWebViewOptions( + // useShouldOverrideUrlLoading: true, + // javaScriptEnabled: true, + // ), + // android: AndroidInAppWebViewOptions(), + // ios: IOSInAppWebViewOptions(), + // ), + // ), + // ), + // ), + // ); + } + + Future> fetchUaepassToken(String code) async { + const String tokenUrl = "https://stg-id.uaepass.ae/idshub/token"; + + const String clientId = "sandbox_stage"; + const String clientSecret = "sandbox_stage"; // For POC both are same + const String redirectUri = "http://localhost:55227/#/login"; // use your redirect URI + + // Encode Basic Auth: base64(clientId:clientSecret) + final String basicAuth = + 'Basic ${base64Encode(utf8.encode("$clientId:$clientSecret"))}'; + + final response = await http.post( + Uri.parse(tokenUrl), + headers: { + "Authorization": basicAuth, + "Content-Type": "application/x-www-form-urlencoded", + }, + body: { + "grant_type": "authorization_code", + "redirect_uri": redirectUri, + "code": code, + }, + ); + + print("🔍 UAE PASS TOKEN RESPONSE → ${response.statusCode}"); + print("📩 Response Body → ${response.body}"); + + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + throw Exception("Failed to fetch token: ${response.body}"); + } + } + + Future _callUaepassTokenAPI(BuildContext context,WidgetRef ref, String code) async { + try { + final String safeCode = (code is String) ? code : code?.toString() ?? ''; + + print("🔐 Calling UAE PASS Token API with code: $safeCode"); + + // TODO: Implement your API call here + // Example: + // final token = await ref.read(authProvider.notifier).loginWithUaepass(code); + + // final tokenData = await fetchUaepassToken(code); + // + // final accessToken = tokenData['access_token']; + // final refreshToken = tokenData['refresh_token']; + // print("✅ Access Token: $accessToken"); + // print("🔄 Refresh Token: $refreshToken"); + // final userInfo = await fetchUaepassUserInfo(accessToken); + // + // print("👤 UAE PASS User Info:"); + // print(userInfo); + // + await checkUserWithUhid( + code: safeCode, + ref:ref, + context: context, + ); + print("✅ Token received and processed successfully"); + } catch (e) { + print("❌ UAE PASS token exchange failed: $e"); + } + } + + Future> fetchUaepassUserInfo(String accessToken) async { + const String userInfoUrl = "https://stg-id.uaepass.ae/idshub/userinfo"; + + final response = await http.get( + Uri.parse(userInfoUrl), + headers: { + "Authorization": "Bearer $accessToken", + "Accept": "application/json", + }, + ); + + print("🔍 USER INFO RESPONSE → ${response.statusCode}"); + print("📩 Response Body → ${response.body}"); + + if (response.statusCode == 200) { + return jsonDecode(response.body); + } else { + throw Exception("Failed to fetch user info: ${response.body}"); + } + } + + Future loginWithProviderAllInOne(String provider) async { + try { + // NOTE: no redirectUrl named parameter here + final authData = + await pb.collection('users').authWithOAuth2(provider, (url) async { + await launchUrl(url); + }); + print('PB token: ${pb.authStore.token}'); + print('User: ${pb.authStore.model}'); + } catch (e) { + print('OAuth error: $e'); + } + } + + @override Widget build(BuildContext context, WidgetRef ref) { double screenHeight = MediaQuery.of(context).size.height; @@ -844,15 +1104,27 @@ class LoginRoute extends HookConsumerWidget { final uaePass = useMemoized(() => UaePass()); final isInit = useState(false); + final params = GoRouterState.of(context).uri.queryParameters; + final uaePassCode = params['uaeCode']; + + useEffect(() { + if (uaePassCode != null) { + print("🚀 UAE PASS code received: $uaePassCode"); + _callUaepassTokenAPI(context,ref, uaePassCode); + } + return null; + }, [uaePassCode]); + // Initialize UAE PASS once when widget loads useEffect(() { Future.microtask(() async { + // await uaePass.setUpSandbox(); await uaePass.setUpEnvironment( clientId: "sandbox_stage", clientSecret: "sandbox_stage", isProduction: false, - urlScheme: "fcscapp", // fallback for Android custom scheme - redirectUri: "https://fcscapp.onelink.me/LI5k/tdh3znpf", + urlScheme: "fcscUaeStats", // fallback for Android custom scheme + redirectUri: "http://localhost:55227/#/login", scope: "urn:uae:digitalid:profile", language: "en", ); // or setUpEnvironment(...) for prod @@ -860,6 +1132,27 @@ class LoginRoute extends HookConsumerWidget { }); return null; }, []); + + + /// ------------------------------------------------------- + /// POCKETBASE OAUTH AUTO-CLOSE (RESUME DETECTION) + /// ------------------------------------------------------- + useEffect(() { + final observer = _OAuthLifecycleObserver( + pb: pb, + context: context, + ); + + WidgetsBinding.instance.addObserver(observer); + return () => WidgetsBinding.instance.removeObserver(observer); + }, []); + + if (!isInit.value) { + return const Scaffold( + body: Center(child: CircularProgressIndicator()), + ); + } + // useEffect(() { // _uaePass.setUpSandbox(); // equivalent to initState() // return null; // no cleanup needed @@ -1721,7 +2014,10 @@ class LoginRoute extends HookConsumerWidget { child: OutlinedButton( onPressed: () async { // signInWithUAEPASS(); - login(context, ref); + openUaepassLogin(context); + // login(context, ref); + // loginWithProviderAllInOne('oidc'); + // Navigator.push( // context, // MaterialPageRoute(builder: (context) => const UAEPassSignInPage()), @@ -1856,10 +2152,10 @@ class LoginRoute extends HookConsumerWidget { // continueAsGuestBtn, // 15.verticalSpace, // orText, - 15.verticalSpace, - signInWithUAEPassBtn, - 15.verticalSpace, - orText, + // 15.verticalSpace, + // signInWithUAEPassBtn, + // 15.verticalSpace, + // orText, 15.verticalSpace, signInWithGoogleBtn, 15.verticalSpace, @@ -1954,6 +2250,49 @@ class LoginRoute extends HookConsumerWidget { // ), // ); // } + +} + +/// ======================================================= +/// APP LIFECYCLE OBSERVER (AUTO-CLOSE LOGIC) +/// ======================================================= +class _OAuthLifecycleObserver extends WidgetsBindingObserver { + final PocketBase pb; + final BuildContext context; + + _OAuthLifecycleObserver({ + required this.pb, + required this.context, + }); + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _handleResume(); + } + } + + void _handleResume() { + if (pb.authStore.isValid) { + debugPrint('✅ PocketBase OAuth login detected'); + + print("✅ OAuth success detected on resume"); + + final userDetails = pb.authStore.model; + final token = pb.authStore.token; + + print(userDetails); + + if (userDetails == null || token.isEmpty) { + print("❌ Invalid authStore data"); + return; + } + + if (context.mounted) { + context.go('/myhomepage'); + } + } + } } // abstract class _LocalAuthRepo { diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart index 818151d0..986be7fd 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/edit_profile.dart @@ -279,7 +279,7 @@ class _EditProfileState extends ConsumerState { }); print(pickedFile); final String fileExtension = - pickedFile.path.split('.').last.toLowerCase(); + pickedFile.name.split('.').last.toLowerCase(); print('fileExtension $fileExtension'); if (fileExtension == 'jpg' || fileExtension == 'jpeg' || @@ -519,6 +519,101 @@ class _EditProfileState extends ConsumerState { // } } + // Future showConfirmationDialog(BuildContext context) async { + // // String? _validateDropdown(ISOCountry? value) { + // // if (value == null) { + // // return context.translate('Please select a country', 'يرجى اختيار دولة'); + // // } + // // return null; + // // } + // + // // if (_validateDropdown(_selectedCountry) == null && + // // _validatePreferredLang(_selectedLanguage) == null) { + // // + // // } + // try { + // setState(() => isLoader = true); + // + // // String userID = userId; + // // final userId = pb.authStore.model!.id; + // + // print('checkin'); + // + // final body = { + // 'country_region': context.translate( + // _selectedCountry?.nameEN ?? '', + // _selectedCountry?.nameAR ?? '', + // ), + // 'language': _selectedLanguage == 'English' ? 'en' : 'ar', + // }; + // print('checkin $body'); + // + // final List files = []; + // + // if (_profileImage != null) { + // final Uint8List bytes = await _profileImage!.readAsBytes(); + // print('Uint8List $bytes'); + // + // files.add( + // http.MultipartFile.fromBytes( + // 'avatar', + // bytes, + // filename: _profileImage!.path.split('/').last, // ✅ FIX + // ), + // ); + // } + // + // print('_profileImage $body'); + // + // await pb.collection('users').update( + // userId, + // body: body, + // files: files, + // ); + // + // if (!mounted) return; + // setState(() => isLoader = false); + // print('mounted'); + // String preferredLang = _selectedLanguage ?? ''; + // if (changedPreferredLanguage == 1) { + // print(_selectedLanguage); + // preferredLang = _selectedLanguage == 'English' ? 'en' : 'ar'; + // if (preferredLang == 'ar') { + // ref.read(localeProvider.notifier).setLocale(const Locale('ar')); + // } else { + // ref.read(localeProvider.notifier).setLocale(const Locale('en')); + // } + // } + // _resetFormFields(); + // + // SchedulerBinding.instance.addPostFrameCallback((_) { + // ScaffoldMessenger.of(context).showSnackBar( + // SnackBar( + // content: Text( + // AppLocalizations.of(context)!.profile_updated_successfully, + // style: TextStyle( + // fontFamily: context.translate( + // 'Roboto', + // 'NotoKufi', + // )), + // )), + // ); + // }); + // + // context.go('/myhomepage'); + // } catch (e, st) { + // debugPrint('❌ Update error: $e'); + // debugPrint('$st'); + // + // if (!mounted) return; + // setState(() => isLoader = false); + // + // ScaffoldMessenger.of(context).showSnackBar( + // const SnackBar(content: Text('Failed to update profile')), + // ); + // } + // } + void _resetFormFields() { print('reset'); setState(() { diff --git a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart index f038f009..e1df4463 100644 --- a/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart +++ b/lib/presentation/routes/drawer_routes/Drawer Items/manage_users.dart @@ -13,6 +13,7 @@ import 'package:uae_stat/config/theme/theme_provider.dart'; import 'package:uae_stat/config/toggle_lang_service.dart'; import 'package:uae_stat/domain/use_cases/language.dart'; import 'package:uae_stat/infrastructure/services/img_asset_paths/icons/misc_icon_asset_path.dart'; +import 'package:uae_stat/infrastructure/services/pocketbase_service.dart'; import 'package:uae_stat/presentation/components/indicators/locale_provider.dart'; import 'package:uae_stat/presentation/components/my_bottom_nav_bar.dart'; @@ -55,60 +56,50 @@ class _ManageUserRouterState extends ConsumerState { void initState() { super.initState(); _userService = UserService(); - fetchUnverifiedUsers(); + fetchAllUsers(); } - Future fetchUnverifiedUsers() async { + Future fetchAllUsers() async { try { - // await _pb.admins - // .authWithPassword('pb@venbainfotech.com', 'pb@venbainfotech.com'); + setState(() => isLoading = true); - final auth = ref.watch(authProvider); - - // await pb.admins.authWithPassword( - // auth.email, - // auth.password, + // await _pb.admins.authWithPassword( + // 'pb@venbainfotech.com', + // 'pb@venbainfotech.com', // ); - final themeService = ThemeBaseService(); - // final adminToken = await themeService.getAdminToken( - // email: auth.email, - // password: auth.password, - // ); - final result = await _pb.collection('users').getFullList( - filter: 'role="user"', - ); + final records = await _pb.collection('users').getFullList( + filter: 'role="user"', + headers: { + 'Authorization': 'Bearer ${PocketBaseService.authStore.token}', + }, + ); + + final users = records.map((record) { + return User( + id: record.id, + userName: record.data['uname'] ?? '', + emailId: record.data['email'] ?? '', + registrationDate: DateFormat('dd/MM/yyyy') + .format(DateTime.parse(record.created)), + status: record.data['status'] ?? 'Pending', + ); + }).toList(); setState(() { - userData = result.map((record) { - final createdDate = DateTime.parse(record.created) - .add(Duration(hours: 4)); // Parse the created date - final formattedDate = DateFormat('dd/MM/yyyy').format(createdDate); - return User( - id: record.id, // Correctly passing the ID - userName: record.getStringValue('uname'), - emailId: record.getStringValue('email'), - registrationDate: formattedDate, - status: record.getStringValue('status'), - ); - }).toList(); - - userData.sort((a, b) { - DateTime dateA = DateFormat('dd/MM/yyyy').parse(a.registrationDate); - DateTime dateB = DateFormat('dd/MM/yyyy').parse(b.registrationDate); - return dateB.compareTo(dateA); - }); - - filteredUserData = List.from(userData); - + userData = users; + filteredUserData = users; isLoading = false; }); - } catch (e) { - isLoading = false; - print('Error fetching unverified users: $e'); + } catch (e, st) { + debugPrint('❌ ERROR FETCHING USERS: $e'); + debugPrint('$st'); + setState(() => isLoading = false); } } + + // Method to filter the list based on the search input void filterUsers(String query) { print('Filterd User: $query'); @@ -123,10 +114,10 @@ class _ManageUserRouterState extends ConsumerState { } void _sort( - Comparable? Function(User user) getField, - int columnIndex, - bool ascending, - ) { + Comparable? Function(User user) getField, + int columnIndex, + bool ascending, + ) { setState(() { _sortColumnIndex = columnIndex; _isAscending = ascending; @@ -159,29 +150,32 @@ class _ManageUserRouterState extends ConsumerState { 'verified': newStatus == 'Approved' ? true : false, 'reviewed': true, }, + headers: { + 'Authorization': 'Bearer ${PocketBaseService.authStore.token}', + }, ); // Update local state // setState(() { // user.status = newStatus; // }); - fetchUnverifiedUsers(); - setState(() { - isLoading = false; - }); + if (!mounted) return; + setState(() => isLoading = false); + fetchAllUsers(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( AppLocalizations.of(context)!.status_success, style: TextStyle( fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - )), + 'Roboto', + 'NotoKufi', + )), ), ), ); } catch (e) { + if (!mounted) return; setState(() { isLoading = false; }); @@ -189,14 +183,14 @@ class _ManageUserRouterState extends ConsumerState { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - context.translate( - 'Error updating status: $e', '$e خطأ في تحديث الحالة: '), - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - )), - )), + context.translate( + 'Error updating status: $e', '$e خطأ في تحديث الحالة: '), + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + )), + )), ); } } @@ -216,7 +210,7 @@ class _ManageUserRouterState extends ConsumerState { borderRadius: BorderRadius.circular(5.0), // Rounded corners ), contentPadding: - EdgeInsets.zero, // Ensure no padding issues with close icon + EdgeInsets.zero, // Ensure no padding issues with close icon content: Stack( children: [ Padding( @@ -247,7 +241,7 @@ class _ManageUserRouterState extends ConsumerState { 'NotoKufi', ), fontWeight: - FontWeight.bold, // Makes newStatus bold + FontWeight.bold, // Makes newStatus bold fontSize: 18, color: Color(0xFF898C81), ), @@ -316,11 +310,11 @@ class _ManageUserRouterState extends ConsumerState { }, style: TextButton.styleFrom( backgroundColor: - Color(0xFFAA8E83), // Set background color + Color(0xFFAA8E83), // Set background color foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: - BorderRadius.circular(10.0), // Set text color + BorderRadius.circular(10.0), // Set text color ), ), child: Text( @@ -371,7 +365,7 @@ class _ManageUserRouterState extends ConsumerState { MediaQuery.of(context).platformBrightness == Brightness.dark); final themeString = isDark ? 'dark' : 'light'; - fetchUnverifiedUsers(); + fetchAllUsers(); // fetchBookmarks(localeCode, themeString); }); @@ -391,7 +385,7 @@ class _ManageUserRouterState extends ConsumerState { MediaQuery.of(context).platformBrightness == Brightness.dark); final themeString = isDark ? 'dark' : 'light'; - fetchUnverifiedUsers(); + fetchAllUsers(); // fetchBookmarks(localeCode, themeString); }); @@ -421,320 +415,314 @@ class _ManageUserRouterState extends ConsumerState { AppLocalizations.of(context)!.manage_user, style: TextStyle( fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - )), + 'Roboto', + 'NotoKufi', + )), ), body: isLoading ? Container( - color: isDarkTheme - ? Colors.black - : Colors.white, // Semi-transparent background - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - margin: EdgeInsets.symmetric( - horizontal: 40), // Left & Right space - child: LinearProgressIndicator( - minHeight: 5, // Adjust thickness - backgroundColor: - Colors.grey[100], // Optional: Background color - valueColor: AlwaysStoppedAnimation( - Color(0xFFAA8E83)), // Loader color - ), - ), - ], - ), - ) - : SingleChildScrollView( - child: Padding( - padding: - const EdgeInsets.only(left: 10.0, right: 10.0, top: 3.0), - child: Column( - children: [ - Container( - height: 40, - decoration: BoxDecoration( - color: isDarkTheme ? Color(0xFF000000) : Colors.white, - borderRadius: BorderRadius.circular(5.0), - border: - Border.all(width: 2, color: Color(0xFFA0A0A0)), - ), - child: Padding( - padding: EdgeInsets.only(left: 5.0), - child: TextField( - decoration: InputDecoration( - hintText: AppLocalizations.of(context)!.search, - hintStyle: TextStyle( - color: isDarkTheme - ? Color(0xFF898C81) - : Color(0xFF898C81), - fontFamily: - context.translate('Roboto', 'NotoKufi'), - fontSize: 18, - // height: 1.8, - ), - prefixIconConstraints: BoxConstraints( - maxWidth: 42, - maxHeight: 42, - ), - prefixIcon: Container( - padding: EdgeInsets.only(right: 5, left: 5), - alignment: Alignment.center, - child: SvgPicture.asset( - MiscIconAssetPath.Search, - semanticsLabel: 'Search', - colorFilter: ColorFilter.mode( - Color(0xFFAA8E83), BlendMode.srcIn), - height: 18, - width: 18, - ), - ), - // contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 18.0), - border: InputBorder.none, - ), - onChanged: filterUsers, - ), - ), - ), - SizedBox(height: myheight / 40), - filteredUserData.isEmpty - ? Center( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox(height: myheight / 5), - - // Icon(Icons.search, - // size: 60, color: Colors.grey), - Image.asset( - MiscIconAssetPath.group, - width: 60, - height: 60, - ), - - SizedBox(height: 15), - // Space between icon and text - Text( - AppLocalizations.of(context)! - .no_result_found, - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - ), - fontSize: 24, - fontWeight: FontWeight.bold, - color: Colors.grey[700], - ), - ), - - SizedBox(height: 12), // Space between texts - FittedBox( - child: Text( - context.translate( - "We couldn't find anything matching your search.", - 'لم نعثر على أي شيء يطابق بحثك.'), - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - ), - fontSize: 18, - color: Color(0xFF898C81)), - textAlign: TextAlign.center, - ), - ), - ], - ), - ), - ) - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: ConstrainedBox( - constraints: BoxConstraints( - minWidth: MediaQuery.of(context).size.width, - ), - child: DataTable( - sortColumnIndex: _sortColumnIndex, - sortAscending: _isAscending, - columns: [ - DataColumn( - label: Text( - AppLocalizations.of(context)!.user_name, - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - )), - ), - onSort: (columnIndex, ascending) { - _sort( - (user) => - user.userName.toLowerCase(), - columnIndex, - ascending); - }, - ), - DataColumn( - label: Text( - AppLocalizations.of(context)!.email_id, - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - )), - ), - onSort: (columnIndex, ascending) { - _sort( - (user) => - user.emailId.toLowerCase(), - columnIndex, - ascending); - }, - ), - DataColumn( - label: Text( - AppLocalizations.of(context)!.reg_date, - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - )), - ), - onSort: (columnIndex, ascending) { - _sort( - (user) => DateFormat('dd/MM/yyyy') - .parse(user.registrationDate), - columnIndex, - ascending, - ); - }, - ), - DataColumn( - label: Text( - AppLocalizations.of(context)!.status, - style: TextStyle( - fontFamily: context.translate( - 'Roboto', - 'NotoKufi', - )), - ), - onSort: (columnIndex, ascending) { - _sort( - (user) => user.status.toLowerCase(), - columnIndex, - ascending); - }, - ), - ], - rows: filteredUserData.isEmpty - ? [ - DataRow( - cells: List.generate( - 4, // Ensure it matches the number of DataColumns - (index) => DataCell( - index == 0 - ? Text( - AppLocalizations.of( - context)! - .no_result_found, - style: TextStyle( - fontFamily: context - .translate( - 'Roboto', - 'NotoKufi', - ), - fontStyle: FontStyle - .italic), - ) - : const Text( - ''), // Empty cells for other columns - placeholder: true, - ), - ), - ), - ] - : filteredUserData.map((user) { - return DataRow( - cells: [ - DataCell( - Text( - user.userName, - style: TextStyle( - fontFamily: - context.translate( - 'Roboto', - 'NotoKufi', - )), - ), - ), - DataCell(Text( - user.emailId, - style: TextStyle( - fontFamily: - context.translate( - 'Roboto', - 'NotoKufi', - )), - )), - DataCell(Text( - user.registrationDate, - style: TextStyle( - fontFamily: - context.translate( - 'Roboto', - 'NotoKufi', - )), - )), - DataCell( - DropdownButton( - value: user.status, - items: statusOptions.entries - .map((status) { - return DropdownMenuItem< - String>( - value: status.key, - child: Text( - status.value, - style: TextStyle( - fontFamily: - context.translate( - 'Roboto', - 'NotoKufi', - ), - color: getStatusColor( - status.key), - ), - ), - ); - }).toList(), - onChanged: (newStatus) { - print(user); - if (newStatus != null) { - _showConfirmationDialog( - user, - newStatus, - statusOptions[ - newStatus]!, - ); - } - }, - ), - ), - ], - ); - }).toList(), - ), - ), - ), - ], - ), + color: isDarkTheme + ? Colors.black + : Colors.white, // Semi-transparent background + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 40), // Left & Right space + child: LinearProgressIndicator( + minHeight: 5, // Adjust thickness + backgroundColor: + Colors.grey[100], // Optional: Background color + valueColor: AlwaysStoppedAnimation( + Color(0xFFAA8E83)), // Loader color ), ), + ], + ), + ) + : SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.only(left: 10.0, right: 10.0, top: 3.0), + child: Column( + children: [ + Container( + height: 40, + decoration: BoxDecoration( + color: isDarkTheme ? Color(0xFF000000) : Colors.white, + borderRadius: BorderRadius.circular(5.0), + border: + Border.all(width: 2, color: Color(0xFFA0A0A0)), + ), + child: Padding( + padding: EdgeInsets.only(left: 5.0), + child: TextField( + decoration: InputDecoration( + hintText: AppLocalizations.of(context)!.search, + hintStyle: TextStyle( + color: isDarkTheme + ? Color(0xFF898C81) + : Color(0xFF898C81), + fontFamily: + context.translate('Roboto', 'NotoKufi'), + fontSize: 18, + // height: 1.8, + ), + prefixIconConstraints: BoxConstraints( + maxWidth: 42, + maxHeight: 42, + ), + prefixIcon: Container( + padding: EdgeInsets.only(right: 5, left: 5), + alignment: Alignment.center, + child: SvgPicture.asset( + MiscIconAssetPath.Search, + semanticsLabel: 'Search', + colorFilter: ColorFilter.mode( + Color(0xFFAA8E83), BlendMode.srcIn), + height: 18, + width: 18, + ), + ), + // contentPadding: EdgeInsets.symmetric(vertical: 12, horizontal: 18.0), + border: InputBorder.none, + ), + onChanged: filterUsers, + ), + ), + ), + SizedBox(height: myheight / 40), + filteredUserData.isEmpty + ? Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox(height: myheight / 5), + + // Icon(Icons.search, + // size: 60, color: Colors.grey), + Image.asset( + MiscIconAssetPath.group, + width: 60, + height: 60, + ), + + SizedBox(height: 15), + // Space between icon and text + Text( + AppLocalizations.of(context)! + .no_result_found, + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + ), + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.grey[700], + ), + ), + + SizedBox(height: 12), // Space between texts + FittedBox( + child: Text( + context.translate( + "We couldn't find anything matching your search.", + 'لم نعثر على أي شيء يطابق بحثك.'), + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + ), + fontSize: 18, + color: Color(0xFF898C81)), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConstrainedBox( + constraints: BoxConstraints( + minWidth: MediaQuery.of(context).size.width, + ), + child: DataTable( + sortColumnIndex: _sortColumnIndex, + sortAscending: _isAscending, + columns: [ + DataColumn( + label: Text( + AppLocalizations.of(context)!.user_name, + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + )), + ), + onSort: (columnIndex, ascending) { + _sort( + (user) => + user.userName.toLowerCase(), + columnIndex, + ascending); + }, + ), + DataColumn( + label: Text( + AppLocalizations.of(context)!.email_id, + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + )), + ), + onSort: (columnIndex, ascending) { + _sort( + (user) => + user.emailId.toLowerCase(), + columnIndex, + ascending); + }, + ), + DataColumn( + label: Text( + AppLocalizations.of(context)!.reg_date, + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + )), + ), + onSort: (columnIndex, ascending) { + _sort( + (user) => DateFormat('dd/MM/yyyy') + .parse(user.registrationDate), + columnIndex, + ascending, + ); + }, + ), + DataColumn( + label: Text( + AppLocalizations.of(context)!.status, + style: TextStyle( + fontFamily: context.translate( + 'Roboto', + 'NotoKufi', + )), + ), + onSort: (columnIndex, ascending) { + _sort( + (user) => user.status.toLowerCase(), + columnIndex, + ascending); + }, + ), + ], + rows: filteredUserData.isEmpty + ? [ + DataRow( + cells: List.generate( + 4, // Ensure it matches the number of DataColumns + (index) => DataCell( + index == 0 + ? Text( + AppLocalizations.of( + context)! + .no_result_found, + style: TextStyle( + fontFamily: context + .translate( + 'Roboto', + 'NotoKufi', + ), + fontStyle: FontStyle + .italic), + ) + : const Text( + ''), // Empty cells for other columns + placeholder: true, + ), + ), + ), + ] + : filteredUserData.map((user) { + return DataRow( + cells: [ + DataCell( + Text( + user.userName, + style: TextStyle( + fontFamily: + context.translate( + 'Roboto', + 'NotoKufi', + )), + ), + ), + DataCell(Text( + user.emailId, + style: TextStyle( + fontFamily: + context.translate( + 'Roboto', + 'NotoKufi', + )), + )), + DataCell(Text( + user.registrationDate, + style: TextStyle( + fontFamily: + context.translate( + 'Roboto', + 'NotoKufi', + )), + )), + DataCell( + DropdownButton( + value: statusOptions.containsKey(user.status) + ? user.status + : null, + items: statusOptions.entries.map((status) { + return DropdownMenuItem( + value: status.key, + child: Text( + status.value, + style: TextStyle( + fontFamily: context.translate('Roboto', 'NotoKufi'), + color: getStatusColor(status.key), + ), + ), + ); + }).toList(), + onChanged: (newStatus) { + if (newStatus != null) { + _showConfirmationDialog( + user, + newStatus, + statusOptions[newStatus]!, + ); + } + }, + ), + + ), + ], + ); + }).toList(), + ), + ), + ), + ], + ), + ), + ), ), ); } @@ -755,4 +743,4 @@ class User { required this.registrationDate, this.status = 'Pending', }); -} +} \ No newline at end of file diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /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 00000000..c366ea7e --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +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 "uae_stat") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "ae.gov.fcsc.uae_stat") + +# 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 "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>: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) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# 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 00000000..d5bd0164 --- /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 00000000..ea3bde61 --- /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 +#include +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + 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) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_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 00000000..e0f0a47b --- /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 + +// 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 00000000..04204663 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,27 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + flutter_secure_storage_linux + gtk + 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 $) + 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/runner/CMakeLists.txt b/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +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} + "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 preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/linux/runner/main.cc b/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/linux/runner/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/runner/my_application.cc b/linux/runner/my_application.cc new file mode 100644 index 00000000..d6fbe391 --- /dev/null +++ b/linux/runner/my_application.cc @@ -0,0 +1,144 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#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) + +// Called when first Flutter frame received. +static void first_frame_cb(MyApplication* self, FlView *view) +{ + gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); +} + +// 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, "uae_stat"); + 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, "uae_stat"); + } + + gtk_window_set_default_size(window, 1280, 720); + + 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); + GdkRGBA background_color; + // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent. + gdk_rgba_parse(&background_color, "#000000"); + fl_view_set_background_color(view, &background_color); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + // Show the window when Flutter renders. + // Requires the view to be realized so we can start rendering. + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self); + gtk_widget_realize(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 GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// 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_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/runner/my_application.h b/linux/runner/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +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/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 586789f0..d66deaf0 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -20,7 +20,6 @@ import share_plus import shared_preferences_foundation import url_launcher_macos import video_player_avfoundation -import webview_flutter_wkwebview func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) @@ -38,5 +37,4 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) - WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) } diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..309b6242 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..dddb8a30 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 00000000..852fa1a4 --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/pubspec.lock b/pubspec.lock index f69ef45a..4c3a2682 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,26 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f url: "https://pub.dev" source: hosted - version: "80.0.0" + version: "85.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: de9ecbb3ddafd446095f7e833c853aff2fa1682b017921fe63a833f9d6f0e422 + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 url: "https://pub.dev" source: hosted - version: "1.3.54" + version: "1.3.59" analyzer: dependency: transitive description: name: analyzer - sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.6.0" analyzer_plugin: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: "direct main" description: name: android_intent_plus - sha256: dfc1fd3a577205ae8f11e990fb4ece8c90cceabbee56fcf48e463ecf0bd6aae3 + sha256: "2329378af63f49b985cb2e110ac784d08374f1e2b1984be77ba9325b1c8cce11" url: "https://pub.dev" source: hosted - version: "5.3.0" + version: "5.3.1" ansicolor: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: app_links - sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba" + sha256: "5f88447519add627fe1cbcab4fd1da3d4fed15b9baf29f28b22535c95ecee3e8" url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.4.1" app_links_linux: dependency: transitive description: @@ -93,10 +93,10 @@ packages: dependency: "direct main" description: name: appsflyer_sdk - sha256: "21de475f77a827dc063cbe2e76d058a4ac49838513ca78753e630cf568eb7254" + sha256: b5908378c20a3983760ba463060711f7b1363fd26e4d0ccdf8d5bd8352b7ef69 url: "https://pub.dev" source: hosted - version: "6.16.2" + version: "6.17.7+1" archive: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: transitive description: @@ -133,18 +133,18 @@ packages: dependency: "direct main" description: name: boxy - sha256: "71af0cd1bf7889c09787f26219a345aa4f38ccb98384c8ec24189e4d8e746005" + sha256: "569373f23560f5a5dbe53c08a7463a698635e7ac72ba355ff4fa52516c0d2e32" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" build: dependency: transitive description: name: build - sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 + sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.4" build_config: dependency: transitive description: @@ -157,34 +157,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0 + sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.5.4" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99" + sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" url: "https://pub.dev" source: hosted - version: "2.4.15" + version: "2.5.4" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" + sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "9.1.2" built_collection: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: built_value - sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4 + sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d url: "https://pub.dev" source: hosted - version: "8.9.5" + version: "8.12.0" characters: dependency: transitive description: @@ -213,10 +213,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "2.0.4" ci: dependency: transitive description: @@ -245,10 +245,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.11.0" collection: dependency: transitive description: @@ -277,10 +277,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: "04bf81bb0b77de31557b58d052b24b3eee33f09a6e7a8c68a3e247c7df19ec27" + sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec url: "https://pub.dev" source: hosted - version: "6.1.3" + version: "6.1.5" connectivity_plus_platform_interface: dependency: transitive description: @@ -301,18 +301,18 @@ packages: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + sha256: "942a4791cd385a68ccb3b32c71c427aba508a1bb949b86dff2adbe4049f16239" url: "https://pub.dev" source: hosted - version: "0.3.4+2" + version: "0.3.5" crypto: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" csslib: dependency: transitive description: @@ -365,10 +365,10 @@ packages: dependency: transitive description: name: custom_lint_visitor - sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" + sha256: "4a86a0d8415a91fbb8298d6ef03e9034dc8e323a599ddc4120a0e36c433983a2" url: "https://pub.dev" source: hosted - version: "1.0.0+7.3.0" + version: "1.0.0+7.7.0" d_chart: dependency: "direct main" description: @@ -381,10 +381,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "27eb0ae77836989a3bc541ce55595e8ceee0992807f14511552a898ddd0d88ac" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.1" dartz: dependency: transitive description: @@ -405,26 +405,26 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53" + sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" url: "https://pub.dev" source: hosted - version: "11.4.0" + version: "11.5.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f url: "https://pub.dev" source: hosted - version: "7.0.2" + version: "7.0.3" dio: dependency: transitive description: name: dio - sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 url: "https://pub.dev" source: hosted - version: "5.8.0+1" + version: "5.9.0" dio_web_adapter: dependency: transitive description: @@ -484,10 +484,10 @@ packages: dependency: "direct main" description: name: fast_immutable_collections - sha256: "95a69b9380483dff49ae2c12c9eb92e2b4e1aeff481a33c2a20883471771598a" + sha256: "19f70498af299cbce5ff919dbbecd5abfd9d0c28139004f68d3810ce23dedfb3" url: "https://pub.dev" source: hosted - version: "11.0.3" + version: "11.1.0" ffi: dependency: transitive description: @@ -508,82 +508,82 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + sha256: "80a877f5ec570c4fb3b40720a70b6f31e8bb1315a464b4d3e92fe82754d4b21a" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + sha256: "44f24d102e368370951b98ffe86c7325b38349e634578312976607d28cc6d747" url: "https://pub.dev" source: hosted - version: "0.9.4+2" + version: "0.9.4+6" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.7.0" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" url: "https://pub.dev" source: hosted - version: "0.9.3+4" + version: "0.9.3+5" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "017d17d9915670e6117497e640b2859e0b868026ea36bf3a57feb28c3b97debe" + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" url: "https://pub.dev" source: hosted - version: "3.13.0" + version: "3.15.2" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.2" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: "129a34d1e0fb62e2b488d988a1fc26cc15636357e50944ffee2862efe8929b23" + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" url: "https://pub.dev" source: hosted - version: "2.22.0" + version: "2.24.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "5f8918848ee0c8eb172fc7698619b2bcd7dda9ade8b93522c6297dd8f9178356" + sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" url: "https://pub.dev" source: hosted - version: "15.2.5" + version: "15.2.10" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "0bbea00680249595fc896e7313a2bd90bd55be6e0abbe8b9a39d81b6b306acb6" + sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" url: "https://pub.dev" source: hosted - version: "4.6.5" + version: "4.6.10" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: ffb392ce2a7e8439cd0a9a80e3c702194e73c927e5c7b4f0adf6faa00b245b17 + sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" url: "https://pub.dev" source: hosted - version: "3.10.5" + version: "3.10.10" fixnum: dependency: transitive description: @@ -617,18 +617,18 @@ packages: dependency: "direct main" description: name: flutter_hooks - sha256: b772e710d16d7a20c0740c4f855095026b31c7eb5ba3ab67d2bd52021cd9461d + sha256: "8ae1f090e5f4ef5cfa6670ce1ab5dddadd33f3533a7f9ba19d9f958aa2a89f42" url: "https://pub.dev" source: hosted - version: "0.21.2" + version: "0.21.3+1" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c + sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" url: "https://pub.dev" source: hosted - version: "0.14.3" + version: "0.14.4" flutter_lints: dependency: "direct dev" description: @@ -654,10 +654,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3" + sha256: "306f0596590e077338312f38837f595c04f28d6cdeeac392d3d74df2f0003687" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.0.32" flutter_rating_bar: dependency: "direct main" description: @@ -734,10 +734,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: "055de8921be7b8e8b98a233c7a5ef84b3a6fcc32f46f1ebf5b9bb3576d108355" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.2.2" flutter_test: dependency: "direct dev" description: flutter @@ -747,10 +747,10 @@ packages: dependency: "direct main" description: name: flutter_uae_pass - sha256: c0e5de77e01b8f26cddcad5e00b530c680bff280acf3b4851d18e974bcfdbdf2 + sha256: "8dc7fe2c063f2e656a63da9dbed8b0622e5db3d67947a3286e9ada6a02397a12" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter_web_plugins: dependency: transitive description: flutter @@ -760,10 +760,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + sha256: "90778fe0497fe3a09166e8cf2e0867310ff434b794526589e77ec03cf08ba8e8" url: "https://pub.dev" source: hosted - version: "8.2.12" + version: "8.2.14" freezed: dependency: "direct dev" description: @@ -792,10 +792,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 + sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9 url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.3.0" glob: dependency: transitive description: @@ -824,42 +824,42 @@ packages: dependency: "direct main" description: name: google_sign_in - sha256: "939a8b58f84c4053811b8c1bc9adbcb59449a15b37958264bbf60020698cca0e" + sha256: "521031b65853b4409b8213c0387d57edaad7e2a949ce6dea0d8b2afc9cb29763" url: "https://pub.dev" source: hosted - version: "7.1.1" + version: "7.2.0" google_sign_in_android: dependency: transitive description: name: google_sign_in_android - sha256: f256b8f0e6c09d135c166fe20b25256e24d60fe1a72e6bdc112a200bd0d555b4 + sha256: eff4ad55abb5de68ef85f1eb9c372638e9b45bea14871cbe8fa8fa78c366335f url: "https://pub.dev" source: hosted - version: "7.0.3" + version: "7.2.5" google_sign_in_ios: dependency: transitive description: name: google_sign_in_ios - sha256: c7ee744ebbcd98353966dbdee735d4fca085226f6bf725c6bea8a5c8fe0055bc + sha256: aee8bb29484611a37607849175a2009d423de6bd7c129326c69fd7068f0b6ed7 url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.4" google_sign_in_platform_interface: dependency: transitive description: name: google_sign_in_platform_interface - sha256: "8736443134d2cccadd4f228d600177cb3947e36683466a6ab96877ce6932885a" + sha256: "7f59208c42b415a3cca203571128d6f84f885fead2d5b53eb65a9e27f2965bb5" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" google_sign_in_web: dependency: transitive description: name: google_sign_in_web - sha256: "09ac306b2787b48f19c857b9f93375b654f774643c75bd6a1a078c85f4f7b468" + sha256: "2fc1f941e6443b2d6984f4056a727a3eaeab15d8ee99ba7125d79029be75a1da" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" graphs: dependency: transitive description: @@ -896,18 +896,18 @@ packages: dependency: transitive description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.6" http: dependency: "direct main" description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -944,74 +944,74 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "8bd392ba8b0c8957a157ae0dc9fcf48c58e6c20908d5880aea1d79734df090e9" + sha256: a1cd1584fae64f6ecca63113fd5450e3483c097cc05e43a2f073330f62adcabe url: "https://pub.dev" source: hosted - version: "0.8.12+22" + version: "0.8.13+8" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + sha256: "997d100ce1dda5b1ba4085194c5e36c9f8a1fb7987f6a36ab677a344cd2dc986" url: "https://pub.dev" source: hosted - version: "0.8.12+2" + version: "0.8.13+2" image_picker_linux: dependency: transitive description: name: image_picker_linux - sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" image_picker_macos: dependency: transitive description: name: image_picker_macos - sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" url: "https://pub.dev" source: hosted - version: "0.2.1+2" + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" url: "https://pub.dev" source: hosted - version: "2.10.1" + version: "2.11.1" image_picker_windows: dependency: transitive description: name: image_picker_windows - sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" injectable: dependency: "direct main" description: name: injectable - sha256: "5e1556ea1d374fe44cbe846414d9bab346285d3d8a1da5877c01ad0774006068" + sha256: "29559f7e3daebf0084597de86a825ae7f149d9e30264b7fbc71d1069ae82697d" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" injectable_generator: dependency: "direct dev" description: @@ -1048,10 +1048,10 @@ packages: dependency: transitive description: name: js - sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" url: "https://pub.dev" source: hosted - version: "0.7.1" + version: "0.7.2" json_annotation: dependency: "direct main" description: @@ -1064,10 +1064,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: "81f04dee10969f89f604e1249382d46b97a1ccad53872875369622b5bfc9e58a" + sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c url: "https://pub.dev" source: hosted - version: "6.9.4" + version: "6.9.5" jwt_decoder: dependency: "direct main" description: @@ -1080,26 +1080,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -1120,10 +1120,10 @@ packages: dependency: "direct main" description: name: mailer - sha256: c6ae65c1d40cfe9c06fa50ff9be6c1cfb18cca60a7bb935e318163bd7f1f0d17 + sha256: c3b934c0e800ddc946167c0123a900eba5acd009abb73648d0191a742542f2b4 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.6.0" marquee: dependency: "direct main" description: @@ -1144,10 +1144,10 @@ packages: dependency: "direct main" description: name: material_charts - sha256: f30b2410c00cc946440e606600b7b72d107737ec508a1300fbc4119820a0f454 + sha256: d6a96295abe39eeef42cc6c49e4f2af69c91abd0b6b0c8f46d8ff5b48531e5a9 url: "https://pub.dev" source: hosted - version: "0.0.28" + version: "0.0.39" material_color_utilities: dependency: transitive description: @@ -1192,26 +1192,26 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" url: "https://pub.dev" source: hosted - version: "8.3.0" + version: "8.3.1" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" path: dependency: transitive description: @@ -1248,18 +1248,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12" + sha256: "95c68a74d3cab950fd0ed8073d9fab15c1c06eb1f3eec68676e87aabc9ecee5a" url: "https://pub.dev" source: hosted - version: "2.2.16" + version: "2.2.21" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: "97390a0719146c7c3e71b6866c34f1cde92685933165c1c671984390d2aca776" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.4" path_provider_linux: dependency: transitive description: @@ -1288,10 +1288,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "7.0.1" platform: dependency: transitive description: @@ -1320,18 +1320,18 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" provider: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -1447,26 +1447,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad" + sha256: "07d552dbe8e71ed720e5205e760438ff4ecfb76ec3b32ea664350e2ca4b0c43b" url: "https://pub.dev" source: hosted - version: "2.4.8" + version: "2.4.16" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: @@ -1539,10 +1539,10 @@ packages: dependency: transitive description: name: source_helper - sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" + sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca url: "https://pub.dev" source: hosted - version: "1.3.5" + version: "1.3.7" source_span: dependency: transitive description: @@ -1551,14 +1551,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" stack_trace: dependency: transitive description: @@ -1611,10 +1603,10 @@ packages: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.6" the_validator: dependency: "direct main" description: @@ -1635,10 +1627,10 @@ packages: dependency: "direct main" description: name: tutorial_coach_mark - sha256: "2c77c0b00bbe7d5b8a6d31cb9e03d44bf77dfe7ba6514cc2b546886d024a9945" + sha256: "5a325d53bcf16ce7a969e2ab8d4dc9610f39ee3eab2b3cc57d5c98936129b891" url: "https://pub.dev" source: hosted - version: "1.2.13" + version: "1.3.3" typed_data: dependency: transitive description: @@ -1651,50 +1643,50 @@ packages: dependency: transitive description: name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.1" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4" + sha256: dff5e50339bf30b06d7950b50fda58164d3d8c40042b104ed041ddc520fbff28 url: "https://pub.dev" source: hosted - version: "6.3.15" + version: "6.3.25" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.6" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -1707,34 +1699,34 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" uuid: dependency: transitive description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.5.2" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de" + sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 url: "https://pub.dev" source: hosted - version: "1.1.18" + version: "1.1.19" vector_graphics_codec: dependency: transitive description: @@ -1747,74 +1739,74 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.19" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" video_player: dependency: "direct main" description: name: video_player - sha256: "48941c8b05732f9582116b1c01850b74dbee1d8520cd7e34ad4609d6df666845" + sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf" url: "https://pub.dev" source: hosted - version: "2.9.3" + version: "2.10.1" video_player_android: dependency: transitive description: name: video_player_android - sha256: d949a83d1a333178eb3a9683f70afa41b7771c2bfa4cedd894fe836942de06b1 + sha256: "36913f94430b474c4a9033d59b7552b800e736a8521e7166e84895ddcedd0b03" url: "https://pub.dev" source: hosted - version: "2.8.1" + version: "2.8.19" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "84b4752745eeccb6e75865c9aab39b3d28eb27ba5726d352d45db8297fbd75bc" + sha256: "6bced1739cf1f96f03058118adb8ac0dd6f96aa1a1a6e526424ab92fd2a6a77d" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.8.7" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: df534476c341ab2c6a835078066fc681b8265048addd853a1e3c78740316a844 + sha256: "57c5d73173f76d801129d0531c2774052c5a7c11ccb962f1830630decd9f24ec" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.6.0" video_player_web: dependency: transitive description: name: video_player_web - sha256: "3ef40ea6d72434edbfdba4624b90fd3a80a0740d260667d91e7ecd2d79e13476" + sha256: "9f3c00be2ef9b76a95d94ac5119fb843dca6f2c69e6c9968f6f2b6c9e7afbdeb" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.4.0" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.4" web: dependency: transitive description: @@ -1827,58 +1819,26 @@ packages: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.2" - webview_flutter: - dependency: "direct main" - description: - name: webview_flutter - sha256: c3e4fe614b1c814950ad07186007eff2f2e5dd2935eba7b9a9a1af8e5885f1ba - url: "https://pub.dev" - source: hosted - version: "4.13.0" - webview_flutter_android: - dependency: transitive - description: - name: webview_flutter_android - sha256: "9a25f6b4313978ba1c2cda03a242eea17848174912cfb4d2d8ee84a556f248e3" - url: "https://pub.dev" - source: hosted - version: "4.10.1" - webview_flutter_platform_interface: - dependency: transitive - description: - name: webview_flutter_platform_interface - sha256: "63d26ee3aca7256a83ccb576a50272edd7cfc80573a4305caa98985feb493ee0" - url: "https://pub.dev" - source: hosted - version: "2.14.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f - url: "https://pub.dev" - source: hosted - version: "3.23.0" + version: "3.0.3" win32: dependency: transitive description: name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "5.15.0" win32_registry: dependency: transitive description: @@ -1899,10 +1859,10 @@ packages: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.6.1" yaml: dependency: transitive description: @@ -1912,5 +1872,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index d58c86d3..c455005f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: uae_stat description: "View statistics about the UAE from the Federal Center for Statistics and Competitiveness." publish_to: "none" -version: 1.1.0+7 +version: 1.2.6+14 #version: 1.0.16+17 #version: 1.0.6+6 @@ -41,7 +41,7 @@ dependencies: flutter_secure_storage: ^10.0.0-beta.4 jwt_decoder: ^2.0.1 fluttertoast: ^8.2.11 - webview_flutter: ^4.13.0 +# webview_flutter: ^4.13.0 video_player: ^2.8.5 flutter_carousel_widget: ^3.1.0 share_plus: ^10.1.1 @@ -81,6 +81,7 @@ dependencies: google_sign_in: ^7.1.1 # flutter_web_auth_2: ^4.1.0 flutter_uae_pass: ^1.1.0 +# flutter_inappwebview: ^6.1.5 # webview_flutter_wkwebview: ^3.23.1 # flutter_appauth: ^10.0.0 diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 00000000..d492d0d9 --- /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 00000000..af53229a --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(uae_stat 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 "uae_stat") + +# 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 00000000..903f4899 --- /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 00000000..0d53d2d4 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,32 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + ConnectivityPlusWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + FlutterSecureStorageWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + 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 00000000..dc139d85 --- /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 00000000..b8dee42b --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,30 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + app_links + connectivity_plus + file_selector_windows + firebase_core + flutter_secure_storage_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 00000000..394917c0 --- /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 00000000..1b692a7a --- /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", "ae.gov.fcsc" "\0" + VALUE "FileDescription", "uae_stat" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "uae_stat" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 ae.gov.fcsc. All rights reserved." "\0" + VALUE "OriginalFilename", "uae_stat.exe" "\0" + VALUE "ProductName", "uae_stat" "\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 00000000..955ee303 --- /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 00000000..6da0652f --- /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 00000000..fae898a9 --- /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"uae_stat", 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 00000000..66a65d1e --- /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 00000000..c04e20ca 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 00000000..153653e8 --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /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(); + } + unsigned 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 00000000..3879d547 --- /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 00000000..60608d0f --- /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 00000000..e901dde6 --- /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_