diff --git a/covas_mobile_new/.gitignore b/covas_mobile_new/.gitignore new file mode 100644 index 0000000..3820a95 --- /dev/null +++ b/covas_mobile_new/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/covas_mobile_new/.metadata b/covas_mobile_new/.metadata new file mode 100644 index 0000000..05a8ab4 --- /dev/null +++ b/covas_mobile_new/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "05db9689081f091050f01aed79f04dce0c750154" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 05db9689081f091050f01aed79f04dce0c750154 + base_revision: 05db9689081f091050f01aed79f04dce0c750154 + - platform: android + create_revision: 05db9689081f091050f01aed79f04dce0c750154 + base_revision: 05db9689081f091050f01aed79f04dce0c750154 + - platform: ios + create_revision: 05db9689081f091050f01aed79f04dce0c750154 + base_revision: 05db9689081f091050f01aed79f04dce0c750154 + - platform: linux + create_revision: 05db9689081f091050f01aed79f04dce0c750154 + base_revision: 05db9689081f091050f01aed79f04dce0c750154 + - platform: macos + create_revision: 05db9689081f091050f01aed79f04dce0c750154 + base_revision: 05db9689081f091050f01aed79f04dce0c750154 + - platform: web + create_revision: 05db9689081f091050f01aed79f04dce0c750154 + base_revision: 05db9689081f091050f01aed79f04dce0c750154 + - platform: windows + create_revision: 05db9689081f091050f01aed79f04dce0c750154 + base_revision: 05db9689081f091050f01aed79f04dce0c750154 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/covas_mobile_new/README.md b/covas_mobile_new/README.md new file mode 100644 index 0000000..5d96d02 --- /dev/null +++ b/covas_mobile_new/README.md @@ -0,0 +1,16 @@ +# covas_mobile_new + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/covas_mobile_new/analysis_options.yaml b/covas_mobile_new/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/covas_mobile_new/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/covas_mobile_new/android/.gitignore b/covas_mobile_new/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/covas_mobile_new/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/covas_mobile_new/android/app/build.gradle.kts b/covas_mobile_new/android/app/build.gradle.kts new file mode 100644 index 0000000..5bd1701 --- /dev/null +++ b/covas_mobile_new/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 = "com.example.covas_mobile_new" + 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 = "com.example.covas_mobile_new" + // 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/covas_mobile_new/android/app/src/debug/AndroidManifest.xml b/covas_mobile_new/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/covas_mobile_new/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/covas_mobile_new/android/app/src/main/AndroidManifest.xml b/covas_mobile_new/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..42ed197 --- /dev/null +++ b/covas_mobile_new/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/covas_mobile_new/android/app/src/main/kotlin/com/example/covas_mobile_new/MainActivity.kt b/covas_mobile_new/android/app/src/main/kotlin/com/example/covas_mobile_new/MainActivity.kt new file mode 100644 index 0000000..d1df550 --- /dev/null +++ b/covas_mobile_new/android/app/src/main/kotlin/com/example/covas_mobile_new/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.covas_mobile_new + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/covas_mobile_new/android/app/src/main/res/drawable-v21/launch_background.xml b/covas_mobile_new/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/covas_mobile_new/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/covas_mobile_new/android/app/src/main/res/drawable/launch_background.xml b/covas_mobile_new/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/covas_mobile_new/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/covas_mobile_new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/covas_mobile_new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/covas_mobile_new/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/covas_mobile_new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/covas_mobile_new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/covas_mobile_new/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/covas_mobile_new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/covas_mobile_new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/covas_mobile_new/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/covas_mobile_new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/covas_mobile_new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/covas_mobile_new/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/covas_mobile_new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/covas_mobile_new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/covas_mobile_new/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/covas_mobile_new/android/app/src/main/res/values-night/styles.xml b/covas_mobile_new/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/covas_mobile_new/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/covas_mobile_new/android/app/src/main/res/values/styles.xml b/covas_mobile_new/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/covas_mobile_new/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/covas_mobile_new/android/app/src/profile/AndroidManifest.xml b/covas_mobile_new/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/covas_mobile_new/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/covas_mobile_new/android/build.gradle.kts b/covas_mobile_new/android/build.gradle.kts new file mode 100644 index 0000000..dbee657 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/android/build/reports/problems/problems-report.html b/covas_mobile_new/android/build/reports/problems/problems-report.html new file mode 100644 index 0000000..24fcec2 --- /dev/null +++ b/covas_mobile_new/android/build/reports/problems/problems-report.html @@ -0,0 +1,663 @@ + + + + + + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/covas_mobile_new/android/gradle.properties b/covas_mobile_new/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/covas_mobile_new/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/covas_mobile_new/android/gradle/wrapper/gradle-wrapper.properties b/covas_mobile_new/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac3b479 --- /dev/null +++ b/covas_mobile_new/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/covas_mobile_new/android/settings.gradle.kts b/covas_mobile_new/android/settings.gradle.kts new file mode 100644 index 0000000..fb605bc --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/ios/.gitignore b/covas_mobile_new/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/covas_mobile_new/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/covas_mobile_new/ios/Flutter/AppFrameworkInfo.plist b/covas_mobile_new/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..1dc6cf7 --- /dev/null +++ b/covas_mobile_new/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/covas_mobile_new/ios/Flutter/Debug.xcconfig b/covas_mobile_new/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/covas_mobile_new/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/covas_mobile_new/ios/Flutter/Release.xcconfig b/covas_mobile_new/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/covas_mobile_new/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/covas_mobile_new/ios/Runner.xcodeproj/project.pbxproj b/covas_mobile_new/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..f6be446 --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 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 */; }; +/* 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 = ""; }; + 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; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 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 */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 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"; + }; + 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)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.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; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.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; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.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)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/covas_mobile_new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/covas_mobile_new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/covas_mobile_new/ios/Runner.xcworkspace/contents.xcworkspacedata b/covas_mobile_new/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/covas_mobile_new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/covas_mobile_new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/covas_mobile_new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/covas_mobile_new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/covas_mobile_new/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/covas_mobile_new/ios/Runner/AppDelegate.swift b/covas_mobile_new/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/covas_mobile_new/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/covas_mobile_new/ios/Runner/Base.lproj/LaunchScreen.storyboard b/covas_mobile_new/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/covas_mobile_new/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/covas_mobile_new/ios/Runner/Base.lproj/Main.storyboard b/covas_mobile_new/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/covas_mobile_new/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/covas_mobile_new/ios/Runner/Info.plist b/covas_mobile_new/ios/Runner/Info.plist new file mode 100644 index 0000000..55b44c4 --- /dev/null +++ b/covas_mobile_new/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Covas Mobile New + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + covas_mobile_new + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/covas_mobile_new/ios/Runner/Runner-Bridging-Header.h b/covas_mobile_new/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/covas_mobile_new/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/covas_mobile_new/ios/RunnerTests/RunnerTests.swift b/covas_mobile_new/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/covas_mobile_new/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/covas_mobile_new/lib/classes/MyDrawer.dart b/covas_mobile_new/lib/classes/MyDrawer.dart new file mode 100644 index 0000000..8fcf62c --- /dev/null +++ b/covas_mobile_new/lib/classes/MyDrawer.dart @@ -0,0 +1,209 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; +import 'dart:io'; +import '../pages/EditProfile.dart'; +import '../pages/EditSettings.dart'; +import '../pages/ListItemMenu.dart'; +import 'alert.dart'; + +import '../variable/globals.dart' as globals; + +import '../pages/LoginDemo.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv +import 'package:encrypt_shared_preferences/provider.dart'; + +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class MyDrawer extends StatelessWidget with ShowAlertDialog { + Future logout(BuildContext context) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + var url = Uri.parse("${globals.api}/token"); + + try { + var response = await http.delete(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=${accessToken}" + }); + + print("Status code logout ${response.statusCode}"); + + if (response.statusCode == 200) { + await prefs.remove("access_token"); + await dotenv.load(fileName: ".env"); // Load .env file + + final keyEncrypt = dotenv.env['KEY_ENCRYPT'] ?? ''; + if (keyEncrypt.isNotEmpty) { + await EncryptedSharedPreferences.initialize(keyEncrypt); + var sharedPref = EncryptedSharedPreferences.getInstance(); + String username = sharedPref.getString("username") ?? ""; + String password = sharedPref.getString("password") ?? ""; + if ((username.isEmpty) || (password.isEmpty)) { + sharedPref.remove("username"); + sharedPref.remove("password"); + } + } + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => LoginDemo()), + (route) => false, // Remove all previous routes + ); + } else { + String errorMessage; + switch (response.statusCode) { + case 400: + errorMessage = "Bad Request: Please check your input."; + break; + case 401: + errorMessage = "Unauthorized: Invalid credentials."; + break; + case 403: + errorMessage = "Forbidden: You don't have permission."; + break; + case 404: + errorMessage = "Not Found: The resource was not found."; + break; + case 500: + errorMessage = + "Server Error: Something went wrong on the server."; + break; + default: + errorMessage = "Unexpected Error: ${response.statusCode}"; + break; + } + print(errorMessage); + showAlertDialog(context, "Error", errorMessage); + } + } catch (e) { + print("Error: $e"); + showAlertDialog( + context, "Error", "An error occurred. Please try again."); + } + } else { + showAlertDialog(context, "Error", "Invalid token."); + } + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + final localeProvider = Provider.of(context); + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + // Drawer Header + DrawerHeader( + decoration: BoxDecoration( + color: Colors.blue, + ), + child: Text( + 'Menu', + style: TextStyle( + color: Colors.white, + fontSize: 24, + ), + ), + ), + // Drawer Items + ListTile( + leading: Icon(Icons.home), + title: Text(loc?.home ?? "Home"), + onTap: () { + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (_) => ListItemMenu())); + + /// Close the drawer + }, + ), + ListTile( + leading: Icon(Icons.settings), + title: Text(loc?.settings ?? 'Settings'), + onTap: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (_) => EditSettings())); // Close the drawer + }, + ), + ListTile( + leading: Icon(Icons.account_circle), + title: Text(loc?.update_profile ?? 'Update profile'), + onTap: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (_) => EditProfile())); // Close the drawer + }, + ), + ListTile( + leading: Icon(Icons.language), + title: Text(loc?.language ?? 'Language'), + onTap: () { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: Text(loc?.select_language ?? 'Select Language'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: Icon(Icons.flag), + title: Text(loc?.french ?? 'Français'), + onTap: () { + Provider.of(context, listen: false) + .setLocale(const Locale('fr')); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.flag_outlined), + title: Text(loc?.english ?? 'English'), + onTap: () { + Provider.of(context, listen: false) + .setLocale(const Locale('en')); + Navigator.of(context).pop(); + }, + ), + ListTile( + leading: Icon(Icons.flag_outlined), + title: Text(loc?.german ?? 'German'), + onTap: () { + Provider.of(context, listen: false) + .setLocale(const Locale('de')); + Navigator.of(context).pop(); + }, + ), + ], + ), + ), + ); + }, + ), + + ListTile( + leading: Icon(Icons.info), + title: Text(loc?.about ?? 'About'), + onTap: () { + showAlertDialog(context, loc?.about ?? 'About', + "Version 0.0.1"); // Close the drawer + }, + ), + ListTile( + leading: Icon(Icons.logout), + title: Text(loc?.log_out ?? 'Log out'), + onTap: () async { + logout(context); +// Close the drawer + }, + ), + ], + ), + ); + } +} diff --git a/covas_mobile_new/lib/classes/ad_helper.dart b/covas_mobile_new/lib/classes/ad_helper.dart new file mode 100644 index 0000000..61bd66c --- /dev/null +++ b/covas_mobile_new/lib/classes/ad_helper.dart @@ -0,0 +1,27 @@ +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +class AdHelper { + static Future createBannerAd(Function setStateCallback) async { + await dotenv.load(fileName: ".env"); + final adUnitId = dotenv.env['AD_UNIT_ID'] ?? ''; + + BannerAd bannerAd = BannerAd( + adUnitId: adUnitId, + size: AdSize.banner, + request: AdRequest(), + listener: BannerAdListener( + onAdLoaded: (ad) { + setStateCallback(() {}); + }, + onAdFailedToLoad: (ad, error) { + print('Banner Ad failed to load: $error'); + ad.dispose(); + }, + ), + ); + + bannerAd.load(); + return bannerAd; + } +} diff --git a/covas_mobile_new/lib/classes/addEventImage.dart b/covas_mobile_new/lib/classes/addEventImage.dart new file mode 100644 index 0000000..f55f3ce --- /dev/null +++ b/covas_mobile_new/lib/classes/addEventImage.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'events.dart'; +import '../variable/globals.dart' as globals; +import 'package:http/http.dart' as http; +import 'dart:io'; +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; + +mixin ShowDescImageAdd on State { + Future addEvents(var events) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + List send = ["toto"]; + + if (accessToken.isNotEmpty) { + var urlPut = Uri.parse("${globals.api}/events"); + print("start date : ${events["start_date"]}"); + var responsePut = await http.put(urlPut, + headers: { + HttpHeaders.cookieHeader: 'access_token=${accessToken}', + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json' + }, + body: jsonEncode({ + 'name': events["name"], + 'place': events["place"], + 'start_date': events['date'], + 'end_date': events['date'], + 'organizers': send, + 'latitude': '0.0', + 'longitude': '0.0', + })); + + print("http put code status : ${responsePut.statusCode}"); + print("http put body : ${responsePut.body}"); + } + } + + void showDescImageAddDialog(BuildContext context, var events) { + // Create AlertDialog + String name = events['name']; + AlertDialog dialog = AlertDialog( + title: Text("Ajouter un evenement"), + content: Text("${name} n'a pas été trouvé. Voulez-vous l'ajouter ? "), + actions: [ + TextButton( + child: Text("Annuler"), + onPressed: () { + Navigator.of(context).pop("Yes, Of course!"); // Return value + }), + TextButton( + child: Text("Oui"), + onPressed: () { + addEvents(events); // Return value + }), + ], + ); + + // Call showDialog function to show dialog. + Future futureValue = showDialog( + context: context, + builder: (BuildContext context) { + return dialog; + }); + } +} diff --git a/covas_mobile_new/lib/classes/alert.dart b/covas_mobile_new/lib/classes/alert.dart new file mode 100644 index 0000000..ed83093 --- /dev/null +++ b/covas_mobile_new/lib/classes/alert.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +mixin ShowAlertDialog { + void showAlertDialog(BuildContext context, String title, String text) { + // Create AlertDialog + AlertDialog dialog = AlertDialog( + title: Text(title), + content: Text(text), + actions: [ + ElevatedButton( + child: Text("OK"), + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20), + textStyle: + TextStyle(fontSize: 15, fontWeight: FontWeight.normal)), + onPressed: () { + Navigator.of(context).pop("Yes, Of course!"); // Return value + }), + ], + ); + + // Call showDialog function to show dialog. + Future futureValue = showDialog( + context: context, + builder: (BuildContext context) { + return dialog; + }); + } +} diff --git a/covas_mobile_new/lib/classes/auth_service.dart b/covas_mobile_new/lib/classes/auth_service.dart new file mode 100644 index 0000000..2cd7a8c --- /dev/null +++ b/covas_mobile_new/lib/classes/auth_service.dart @@ -0,0 +1,141 @@ +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; +import '../variable/globals.dart' as globals; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:encrypt_shared_preferences/provider.dart'; +import '../pages/LoginDemo.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv + +class AuthService { + // Login with username and password + Future login(String username, String password, + {bool rememberMe = false}) async { + final url = Uri.parse("${globals.api}/token"); + + try { + final response = await http.post( + url, + headers: { + 'accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: {"username": username, "password": password}, + ); + + if (response.statusCode == 200 || response.statusCode == 201) { + final prefs = await SharedPreferences.getInstance(); + if (rememberMe) { + await dotenv.load(fileName: ".env"); // Load .env file + + final keyEncrypt = dotenv.env['KEY_ENCRYPT'] ?? ''; + if (keyEncrypt.isNotEmpty) { + await EncryptedSharedPreferences.initialize(keyEncrypt); + var sharedPref = EncryptedSharedPreferences.getInstance(); + sharedPref.setString("username", username); + sharedPref.setString("password", password); + } + } + final cookies = response.headers["set-cookie"]?.split(";") ?? []; + + for (final cookie in cookies) { + final cookieParts = cookie.split(","); + for (final part in cookieParts) { + final keyValue = part.split("="); + if (keyValue.length == 2 && keyValue[0] == "access_token") { + prefs.setString("access_token", keyValue[1]); + } + } + } + return true; + } else { + return false; + } + } catch (e) { + print("Login error: $e"); + return false; + } + } + + // Logout + Future logout() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove("access_token"); + } + + Future isLoggedIn() async { + final prefs = await SharedPreferences.getInstance(); + final accessToken = prefs.getString("access_token"); + + if (accessToken == null || accessToken.isEmpty) { + print("No access token found."); + return false; + } + + print("Checking token validity..."); + var url = Uri.parse("${globals.api}/token"); + + try { + final response = await http.get( + url, + headers: { + HttpHeaders.cookieHeader: "access_token=$accessToken", + "Content-Type": "application/json", + }, + ); + + if (response.statusCode == 200) { + print("Token is valid."); + return true; + } else { + print("Token is invalid. Status code: ${response.statusCode}"); + await dotenv.load(fileName: ".env"); // Load .env file + + final keyEncrypt = dotenv.env['KEY_ENCRYPT'] ?? ''; + if (keyEncrypt.isNotEmpty) { + await EncryptedSharedPreferences.initialize(keyEncrypt); + var sharedPref = EncryptedSharedPreferences.getInstance(); + String username = sharedPref.getString("username") ?? ""; + String password = sharedPref.getString("password") ?? ""; + if ((username.isEmpty) || (password.isEmpty)) { + sharedPref.remove("username"); + sharedPref.remove("password"); + await prefs.remove("access_token"); // Clear invalid token + return false; + } else { + return login(username, password); + } + } else { + return false; + } + } + } catch (e) { + print("Error while checking token: $e"); + return false; + } + } + + Future checkTokenStatus(context) async { + bool loggedIn = await isLoggedIn(); + SharedPreferences prefs = await SharedPreferences.getInstance(); + + if (!loggedIn) { + await prefs.remove("access_token"); // Correctly remove the token + + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => LoginDemo()), + (route) => false, // Remove all previous routes + ); + } + } + + // Get stored access token + Future getAccessToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString("access_token"); + } + + // Login with Google +} diff --git a/covas_mobile_new/lib/classes/eventAdded.dart b/covas_mobile_new/lib/classes/eventAdded.dart new file mode 100644 index 0000000..d2c75b7 --- /dev/null +++ b/covas_mobile_new/lib/classes/eventAdded.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +import '../pages/ListItemMenu.dart'; + +mixin ShowEventDialog on State { + void showEventDialog(BuildContext context, String text) { + // Create AlertDialog + AlertDialog dialog = AlertDialog( + title: Text("Evenement ajoute"), + content: Text(text), + actions: [ + ElevatedButton( + child: Text("OK"), + style: ElevatedButton.styleFrom( + padding: EdgeInsets.symmetric(horizontal: 50, vertical: 20), + textStyle: + TextStyle(fontSize: 15, fontWeight: FontWeight.normal)), + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ListItemMenu())); // Return value + }), + ], + ); + + // Call showDialog function to show dialog. + Future futureValue = showDialog( + context: context, + builder: (BuildContext context) { + return dialog; + }); + } +} diff --git a/covas_mobile_new/lib/classes/events.dart b/covas_mobile_new/lib/classes/events.dart new file mode 100644 index 0000000..8df086e --- /dev/null +++ b/covas_mobile_new/lib/classes/events.dart @@ -0,0 +1,54 @@ +class Events { + String? id; + String? name; + String? place; + String? startDate; + String? endDate; + String? description; + String? link; + String? ticket; + double? latitude; + double? longitude; + List? tags; + List? organizers; + String? imgUrl; + int? interestedCount; + bool? interested; + Events( + {this.place, + this.id, + this.name, + this.startDate, + this.description, + this.endDate, + this.tags, + this.latitude, + this.longitude, + this.organizers, + this.link, + this.ticket, + this.imgUrl, + this.interestedCount, + this.interested}); + + Events.fromJson(Map json) { + id = json['id'] as String?; + name = json['name'] as String?; + place = json['place'] as String?; + startDate = json['start_date'] as String?; + endDate = json['end_date'] as String?; + description = json['description'] as String?; + latitude = (json['latitude'] as num?)?.toDouble(); // Safely cast to double + longitude = + (json['longitude'] as num?)?.toDouble(); // Safely cast to double + tags = (json['tags'] as List?) + ?.cast(); // Convert List to List + organizers = (json['organizers'] as List?) + ?.cast(); // Convert List to List + imgUrl = json['imgUrl'] as String?; + link = json['link'] as String?; + ticket = json['ticket'] as String?; + interested = json['interested'] as bool?; + interestedCount = json['interested_count'] as int?; + } +} diff --git a/covas_mobile_new/lib/classes/getEventImage.dart b/covas_mobile_new/lib/classes/getEventImage.dart new file mode 100644 index 0000000..a0db5dc --- /dev/null +++ b/covas_mobile_new/lib/classes/getEventImage.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'events.dart'; +import '../variable/globals.dart' as globals; +import 'package:http/http.dart' as http; +import 'dart:io'; +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; + +mixin ShowDescImageGet on State { + Future getEvents(var events) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + List send = ["toto"]; + + if (accessToken.isNotEmpty) { + var urlPut = Uri.parse("${globals.api}/events"); + print("start date : ${events["start_date"]}"); + var responsePut = await http.put(urlPut, + headers: { + HttpHeaders.cookieHeader: 'access_token=${accessToken}', + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json' + }, + body: jsonEncode({ + 'name': events["name"], + 'place': events["place"], + 'start_date': events['date'], + 'end_date': events['date'], + 'organizers': send, + 'latitude': '0.0', + 'longitude': '0.0', + })); + + print("http put code status : ${responsePut.statusCode}"); + print("http put body : ${responsePut.body}"); + } + } + + void showDescImageGetDialog(BuildContext context, var events) { + // Create AlertDialog + String name = events['name']; + AlertDialog dialog = AlertDialog( + title: Text("Voir la description de l'evenement"), + content: Text("${name} a été trouvé. Voulez-vous voir sa description ? "), + actions: [ + TextButton( + child: Text("Annuler"), + onPressed: () { + Navigator.of(context).pop("Yes, Of course!"); // Return value + }), + TextButton( + child: Text("Oui"), + onPressed: () { + getEvents(events); // Return value + }), + ], + ); + + // Call showDialog function to show dialog. + Future futureValue = showDialog( + context: context, + builder: (BuildContext context) { + return dialog; + }); + } +} diff --git a/covas_mobile_new/lib/classes/notification_service.dart b/covas_mobile_new/lib/classes/notification_service.dart new file mode 100644 index 0000000..4ff081e --- /dev/null +++ b/covas_mobile_new/lib/classes/notification_service.dart @@ -0,0 +1,91 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:timezone/timezone.dart' as tz; +import 'package:timezone/data/latest_all.dart' as tz; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; + +class NotificationService { + static final FlutterLocalNotificationsPlugin _notificationsPlugin = + FlutterLocalNotificationsPlugin(); + + /// Initialisation (à appeler dans main()) + static Future initialize() async { + tz.initializeTimeZones(); + + const AndroidInitializationSettings androidInitSettings = + AndroidInitializationSettings('@mipmap/ic_launcher'); + + const InitializationSettings initSettings = InitializationSettings( + android: androidInitSettings, + iOS: DarwinInitializationSettings(), + ); + + await _notificationsPlugin.initialize(initSettings); + + // Demande les permissions au lancement + await requestPermissions(); + } + + /// Demander les permissions (Android 13+ et iOS) + static Future requestPermissions() async { + // Android 13+ + final androidImplementation = + _notificationsPlugin.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); + await androidImplementation?.requestNotificationsPermission(); + + // iOS + final iosImplementation = + _notificationsPlugin.resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>(); + await iosImplementation?.requestPermissions( + alert: true, + badge: true, + sound: true, + ); + } + + /// Planifie une notification 1h avant l’évènement + static Future scheduleEventNotification({ + required String eventId, + required String title, + required String body, + required DateTime eventDate, + }) async { + final scheduledDate = eventDate.subtract(const Duration(hours: 1)); + + if (scheduledDate.isBefore(DateTime.now())) { + // Trop tard pour notifier + return; + } + + await _notificationsPlugin.zonedSchedule( + eventId.hashCode, // identifiant unique pour l’évènement + title, + body, + tz.TZDateTime.from(scheduledDate, tz.local), + const NotificationDetails( + android: AndroidNotificationDetails( + 'events_channel', + 'Events', + channelDescription: 'Favorite event notifications ', + importance: Importance.high, + priority: Priority.high, + ), + iOS: DarwinNotificationDetails(), + ), + androidScheduleMode: AndroidScheduleMode + .inexactAllowWhileIdle, // évite l'erreur Exact Alarm + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime, + matchDateTimeComponents: DateTimeComponents.dateAndTime, + ); + } + + /// Annule une notification planifiée + static Future cancel(String eventId) async { + await _notificationsPlugin.cancel(eventId.hashCode); + } +} diff --git a/covas_mobile_new/lib/l10n/app_de.arb b/covas_mobile_new/lib/l10n/app_de.arb new file mode 100644 index 0000000..c6c07b1 --- /dev/null +++ b/covas_mobile_new/lib/l10n/app_de.arb @@ -0,0 +1,143 @@ +{ + "@@locale": "de", + "menu_list": "Veranstaltungsmenü", + "language": "Sprache", + "home": "Startseite", + "settings": "Einstellungen", + "update_profile": "Profil aktualisieren", + "about": "Über", + "log_out": "Abmelden", + "french": "Französisch", + "english": "Englisch", + "german": "Deutsch", + "select_language": "Sprache auswählen", + "search_item": "Nach Element suchen", + "search_tag": "Nach Schlagwörtern suchen", + "search_geographical": "Nach geografischer Zone suchen", + "show_date_field": "Datumsfelder anzeigen", + "hide_date_field": "Datumsfelder ausblenden", + "no_data": "Keine Daten verfügbar", + "search": "Suchen", + "no_events": "Keine Veranstaltungen für diesen Ort verfügbar.", + "start_date": "Anfangsdatum", + "end_date": "Enddatum", + "failed_suggestions": "Vorschläge konnten nicht geladen werden", + "error": "Fehler", + "password_different": "Ein anderes Passwort eingeben", + "create": "Erstellung", + "user_create": "Benutzer wurde erstellt", + "user_update": "Benutzer wurde aktualisiert", + "request_error": "Fehlerhafte Anfrage", + "incorrect_password": "Falsches Passwort", + "unknown_user": "Unbekannter Benutzer", + "disabled_user": "Benutzer deaktiviert", + "invalid_token": "Ungültiger Token", + "internal_error_server": "Interner Serverfehler", + "unknown_error_auth": "Unbekannter Authentifizierungsfehler", + "required_input": "Pflichtfeld", + "create_profile": "Profil erstellen", + "edit_pseudo": "Benutzernamen bearbeiten", + "password": "Passwort", + "enter_password": "Passwort eingeben", + "password_confirmed": "Passwort bestätigt", + "last_name": "Nachname", + "first_name": "Vorname", + "email": "E-Mail", + "edit_last_name": "Nachnamen bearbeiten", + "edit_first_name": "Vornamen bearbeiten", + "edit_email": "E-Mail-Adresse bearbeiten", + "birth_date": "Geburtsdatum", + "edit_birth": "Geburtsdatum bearbeiten", + "create_profile_button": "Profil erstellen", + "take_picture": "Foto aufnehmen", + "error_ia": "Google KI konnte das Bild nicht analysieren. Bitte ein anderes versuchen.", + "no_data_geo": "Keine geografischen Daten", + "response_status_update": "Statuscode-Antwort aktualisieren", + "error_token": "Token-Fehler", + "error_format": "Vom KI geliefertes Datenformat ist fehlerhaft", + "display_picture": "Bild anzeigen", + "analyze_image": "Bildanalyse läuft", + "loading_progress": "Ladefortschritt", + "error_event": "Veranstaltungsfehler", + "no_future_event": "Keine zukünftigen Veranstaltungen", + "error_user": "Benutzerfehler", + "empty_input": "Eingabefeld leer", + "info_event": "Veranstaltungsinfo", + "event_already": "Veranstaltung existiert bereits", + "picture_error": "Bildfehler", + "no_picture_published": "Kein Bild veröffentlicht", + "event_update": "Veranstaltung aktualisiert", + "location": "Ort", + "add_event": "Veranstaltung hinzufügen oder aktualisieren", + "edit_image": "Bilder bearbeiten", + "name": "Name", + "edit_event_name": "Veranstaltungsname bearbeiten", + "start_time": "Startzeit", + "end_time": "Endzeit", + "select_date": "Zum Auswählen eines Datums klicken", + "select_time": "Zum Auswählen einer Uhrzeit klicken", + "tag": "Schlagwörter", + "already_tag": "Dieses Schlagwort ist bereits vorhanden", + "enter_tag": "Ein Schlagwort eingeben", + "organizer": "Veranstalter", + "already_organiser": "Veranstalter bereits vorhanden", + "enter_organizer": "Veranstalter eingeben", + "description": "Beschreibung", + "describe_event": "Veranstaltung beschreiben", + "add": "Hinzufügen", + "different_password_error": "Passwörter stimmen nicht überein", + "update": "Aktualisieren", + "updated": "Aktualisiert", + "settings_updated": "Einstellungen aktualisiert", + "define_kilometer": "Kilometer definieren", + "email_sent": "E-Mail wurde gesendet", + "forgot_password": "Passwort vergessen", + "enter_email": "E-Mail eingeben", + "send_email": "E-Mail senden", + "invalid_cache": "Ungültiger Cache", + "item_date": "Datum : ", + "item_maps": "Karte : ", + "item_organizer": "Veranstalter : ", + "item_description": "Beschreibung : ", + "item_tags": "Schlagwörter : ", + "failed_auth": "Authentifizierung fehlgeschlagen", + "login_page": "Anmeldeseite", + "pseudo": "Benutzername", + "enter_existing_pseudo": "Vorhandenen Benutzernamen eingeben", + "remembr_me": "Angemeldet bleiben", + "new_user": "Neuer Benutzer? Konto erstellen", + "sign_in": "Anmelden", + "map_token": "Mapbox-Zugangstoken ist nicht verfügbar", + "geo_disabled": "Standortdienste sind deaktiviert.", + "permission_denied": "Standortberechtigungen wurden verweigert.", + "enable_permission": "Standortberechtigungen dauerhaft verweigert. Bitte in den Einstellungen aktivieren.", + "no_last_position": "Keine letzte bekannte Position verfügbar.", + "failed_location": "Standort konnte nicht ermittelt werden", + "failed_fetch": "Route konnte nicht abgerufen werden", + "invalid_coordinates_symbol": "Ungültige Koordinaten, Symbol kann nicht hinzugefügt werden.", + "error_symbol": "Fehler beim Hinzufügen des Symbols.", + "position_not_init": "Benutzerposition noch nicht initialisiert. Erneut versuchen.", + "invalid_coordinates": "Ungültige Koordinaten.", + "walking": "Zu Fuß", + "cycling": "Mit dem Fahrrad", + "driving": "Mit dem Auto", + "get_direction": "Wegbeschreibung und Markierungen anzeigen", + "missing_token": "Zugangstoken fehlt", + "geocoding_error": "Fehler bei der Geokodierung", + "no_found_place": "Kein Ort gefunden", + "upload_error": "Fehler beim Hochladen des Bildes", + "event_added": "Veranstaltung hinzugefügt", + "unknown_error": "Unbekannter Fehler", + "app_error": "Anwendungsfehler", + "at": "um", + "to_date": "bis", + "item_link": "Link : ", + "item_ticket": "Abendkarte : ", + "link": "Link", + "edit_link": "Linkereignis bearbeiten", +"ticket": "Abendkarte", +"edit_ticket": "Ticketlink bearbeiten", +"toogle_interest": "Fehler beim Umschalten des Interesses", +"error_update": "Fehler beim Update", +"count_interested": "Anzahl der Interessenten" +} diff --git a/covas_mobile_new/lib/l10n/app_en.arb b/covas_mobile_new/lib/l10n/app_en.arb new file mode 100644 index 0000000..3a1fd28 --- /dev/null +++ b/covas_mobile_new/lib/l10n/app_en.arb @@ -0,0 +1,145 @@ +{ + "@@locale": "en", +"menu_list": "Event list menu", +"language": "Language", +"home": "Home", +"settings": "Settings", +"update_profile": "Update profile", +"about": "About", +"log_out": "Log out", +"french": "French", +"english": "English", +"german": "German", +"select_language": "Select language", +"search_item": "Search by item", +"search_tag": "Search by tags", +"search_geographical": "Search by geographical zone", +"show_date_field": "Show Date Fields", +"hide_date_field": "Hide Date Fields", +"no_data": "No data available", +"search": "Search", +"no_events": "No events available for this location.", +"start_date": "Start date", +"end_date": "End date", +"failed_suggestions": "Failed to load suggestions", +"error":"Error", +"password_different":"Must write a different password", +"create": "Creation", +"user_create": "Your user created", +"user_update": "Your user updated", +"request_error": "Poorly constructed query", +"incorrect_password": "Incorrect password", +"unknown_user": "Unknown user", +"disabled_user": "User disabled", +"invalid_token": "Invalid token", +"internal_error_server": "Internal error server", +"unknown_error_auth": "Unknown error authentification", +"required_input": "Required input", +"create_profile": "Create profile", +"edit_pseudo": "Edit pseudo", +"password":"Password", +"enter_password": "Enter the passord", + "password_confirmed": "Password confirmed", + "last_name": "Last name", +"first_name": "First name", +"email": "Mail", +"edit_last_name": "Edit name", +"edit_first_name": "Edit first name", +"edit_email": "Edit email address", +"birth_date": "Birth date", +"edit_birth": "Edit birth date", +"create_profile_button": "Create profile", +"take_picture": "Take a picture", +"error_ia": "Google AI failed to analyze picture. Retry with another one", +"no_data_geo": "No geographical data", +"response_status_update": "response status code update", +"error_token": "Token error", +"error_format": "Data format error given by AI", +"display_picture": "Display the Picture", +"analyze_image": "Image Analyze in progress", +"loading_progress": "Loading progress", +"error_event": "Event error", +"no_future_event": "No future event", +"error_user": "Error user", +"empty_input": "Empty input", +"info_event": "Event info", +"event_already": "Event already exists", +"picture_error": "Picture error", +"no_picture_published": "No picture published", +"event_update": "Event updated", +"location": "Location", +"add_event": "Add or Update a event", +"edit_image": "Edit pictures", +"name": "Name", +"edit_event_name": "Edit event name", +"start_time": "Start time", +"end_time": "End time", +"select_date": "Click to select a date", +"select_time": "Click to select a time", +"tag": "Tags", +"already_tag": "You have already this tags", +"enter_tag": "Enter a tag", +"organizer": "Organizer", +"already_organiser": "You have already a organizer", +"enter_organizer": "Enter a organizer", +"description": "Description", +"describe_event": "Describe event", +"add": "Add", +"update_profile": "Update profile", +"different_password_error": "Different password", +"update": "Update", +"updated": "Updated", +"settings_updated": "Settings updated", +"define_kilometer": "Define Kilometer", +"settings": "Settings", +"email_sent": "Email has been sent", +"forgot_password": "Forgot password", +"enter_email": "Enter the email", +"send_email": "Send email", +"invalid_cache": "Invalid cache", +"item_date": "Date : ", +"item_maps": "Maps : ", +"item_organizer": "Organizer : ", +"item_description": "Description : ", +"item_tags": "Tags : ", +"failed_auth": "Authentification failed", +"login_page": "Login page", +"pseudo": "Pseudo", +"enter_existing_pseudo": "Enter a existing pseudo", +"remembr_me": "Remember me", +"new_user": "New User? Create Account", +"sign_in": "Sign in", +"map_token": "Mapbox Access Token is not available", +"geo_disabled": "Location services are disabled.", +"permission_denied":"Location permissions are denied.", +"enable_permission": "Location permissions are permanently denied. Enable them in settings.", +"no_last_position": "No last known position available.", +"failed_location": "Failed to get user location", +"failed_fetch": "Failed to fetch the route", +"invalid_coordinates_symbol": "Invalid coordinates, cannot add symbol.", +"error_symbol": "Error when adding symbol.", +"position_not_init": "User position is not yet initialized. Try again.", +"invalid_coordinates": "Invalid coordinates.", +"walking": "Walking", +"cycling": "Cycling", +"driving": "Driving", +"get_direction": "Get Directions and Markers", +"missing_token": "Missing access token", +"geocoding_error": "Error when geocoding", +"no_found_place": "No found place", +"upload_error": "Error when image uploading", +"event_added": "Event added", +"unknown_error": "Unknown error", +"app_error": "Application error", +"at": "at", +"to_date": "to", +"item_link": "Link : ", +"item_ticket": "Ticket : ", +"link": "Link", +"edit_link": "Edit link name", +"ticket": "Ticket", +"edit_ticket": "Edit ticket link", +"toogle_interest": "Error toggle interest", +"error_update": "Error when updating", +"count_interested": "Interested people number" +} \ No newline at end of file diff --git a/covas_mobile_new/lib/l10n/app_fr.arb b/covas_mobile_new/lib/l10n/app_fr.arb new file mode 100644 index 0000000..c8003a3 --- /dev/null +++ b/covas_mobile_new/lib/l10n/app_fr.arb @@ -0,0 +1,146 @@ +{ + "@@locale": "fr", +"menu_list": "Liste d'évènement", +"language": "Langue", +"home": "Accueil", +"settings": "Paramètres", +"update_profile": "Modifier profil", +"about": "À propos", +"log_out": "Se déconnecter", +"french": "Français", +"english": "Anglais", +"german": "Allemand", +"select_language": "Selectionne la langue", +"search_item": "Recherche par item", +"search_tag": "Recherche par tags", +"search_geographical": "Recherche par zone géographique", +"show_date_field": "Afficher champ date", +"hide_date_field": "Cacher Date Fields", +"no_data": "Aucune donnée disponible", +"search": "Recherche", +"no_events": "Pas d'évènements dans cette localisation", +"start_date": "Date de début", +"end_date": "Date de fin", +"failed_suggestions": "Echec de chargement des suggestions", +"error":"Erreur", +"password_different":"Tu dois écrire un mot de passe different", +"create": "Création", +"user_create": "Votre utilisateur a été créé", +"user_update": "Votre utilisateur a été modifié", +"request_error": "Requête mal construite", +"incorrect_password": "Mot de passe incorrect", +"unknown_user": "Utilisateur inconnu", +"disabled_user": "Utilisateur désactivé", +"invalid_token": "Token invalide", +"internal_error_server": "Erreur interne de serveur", +"unknown_error_auth": "Problème d'authentification inconnu", +"required_input": "Champ requis", +"create_profile": "Creation profil", +"edit_pseudo": "Modifier le pseudo", +"password":"Mot de passe", +"enter_password": "Entrez le password", +"password_confirmed": "Confirmez le mot de passe", +"last_name": "Nom", +"first_name": "Prénom", +"email": "Email", +"edit_last_name": "Modifier le nom", +"edit_first_name": "Modifier le prénom", +"edit_email": "Modifier l'email", +"birth_date": "Date de naissance", +"edit_birth": "Modifier la date de naissance", +"create_profile_button": "Créer le profil", +"take_picture": "Take a picture", +"error_ia": "L'IA de Google n'a pas su analyser l'image. Recommencer avec une autre", +"no_data_geo": "Aucune donnée géographique", +"response_status_update": "Code du statut de réponse de la modification", +"error_token": "Erreur de token", +"error_format": "Erreur de format de donnée fourni par l'IA", +"display_picture": "Display the Picture", +"analyze_image": "Analyse de l'image en cours", +"loading_progress": "Chargement en cours", +"error_event": "Erreur de l'évènement", +"no_future_event": "Évènement non futur", +"error_user": "Erreur de l'utilisateur", +"empty_input": "Champ vide", +"info_event": "Event info", +"event_already": "Event already exists", +"picture_error": "Erreur image", +"no_picture_published": "Image non publiée", +"event_update": "Évènement modifié", +"location": "Lieu", +"add_event": "Ajouter ou modifier un évènement", +"edit_image": "Changer la photo", +"name": "Nom", +"edit_event_name": "Changer le nom de l'évènement", +"start_time": "Heure de début", +"end_time": "Heure de fin", +"select_date": "Cliquer pour selectionner une date", +"select_time": "Cliquer pour selectionner une heure", +"tag": "Tags", +"already_tag": "Tu as déjà entré ce tag", +"enter_tag": "Entrer un tag", +"organizer": "Organisateur", +"already_organiser": "Tu as déjà rentré cet organisateur", +"enter_organizer": "Entrer un organisateur", +"description": "Description", +"describe_event": "Décrire l'évènement", +"add": "Ajouter", +"update_profile": "Modifier le profil", +"different_password_error": "Mot de passe différent", +"update": "Mettre à jour", +"updated": "Mis à jour", +"settings_updated": "Paramètre mis à jour", +"define_kilometer": "Definir un kilomètre", +"settings": "Paramètres", +"email_sent": "Email a été envoyé", +"forgot_password": "Mot de passe oublié", +"enter_email": "Entrez l'email", +"send_email": "Send email", +"invalid_cache": "Cache invalide", +"item_date": "Date : ", +"item_maps": "Carte : ", +"item_organizer": "Organisateurs : ", +"item_description": "Description : ", +"item_tags": "Tags : ", +"failed_auth": "Échec de l'authenticaton", +"login_page": "Page d'authentification", +"pseudo": "Pseudo", +"enter_existing_pseudo": "Entrez un pseudo existant", +"remembr_me": "Se souvenir de moi", +"new_user": "Nouvel utilisateur ? Créer un compte", +"sign_in": "Se connecter", +"map_token": "Token d'accès de Mapbox n'est pas disponible", +"geo_disabled": "Les services de localisation sont désactivés.", +"permission_denied":"Les permissions de localisation sont refusées.", +"enable_permission": "Les permissions de localisation sont toujours désactivés. Il faut les désactiver", +"no_last_position": "Aucune position n'est pas disponible.", +"failed_location": "Échec de récupération des données geographique", +"failed_fetch": "Échec de récupération des routes", +"invalid_coordinates_symbol": "Coordonnées invalides. On ne peut pas ajouter le symbole", +"error_symbol": "Erreur lors de l'ajout du symbole", +"position_not_init": "Coordonnées non initialisées. Essaye encore.", +"invalid_coordinates": "Coordonnées invalides", +"walking": "Marche", +"cycling": "Vélo", +"driving": "Voiture", +"get_direction": "Get Directions and Markers", +"missing_token": "Token d'accès manquant", +"geocoding_error": "Erreur lors du geocodage", +"no_found_place": "Lieu introuvable", +"upload_error": "Erreur lors de l'upload d'image", +"event_added": "Évènement ajouté", +"unknown_error": "Erreur inconnue", +"app_error": "Erreur d'application", +"at": "à", +"to_date": "jusqu'à", +"item_link": "Lien : ", +"item_ticket": "Billet : ", +"link": "Lien", +"edit_link": "Editer le lien", +"ticket": "Billet", +"edit_ticket": "Editer le lien du billet", +"toogle_interest": "Erreur de bouton de changement", +"error_update": "Erreur lors de la mise à jour", +"count_interested": "Nombre de personne interessé" + +} \ No newline at end of file diff --git a/covas_mobile_new/lib/l10n/app_localizations.dart b/covas_mobile_new/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..502fa07 --- /dev/null +++ b/covas_mobile_new/lib/l10n/app_localizations.dart @@ -0,0 +1,977 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_de.dart'; +import 'app_localizations_en.dart'; +import 'app_localizations_fr.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('de'), + Locale('en'), + Locale('fr') + ]; + + /// No description provided for @menu_list. + /// + /// In en, this message translates to: + /// **'Event list menu'** + String get menu_list; + + /// No description provided for @language. + /// + /// In en, this message translates to: + /// **'Language'** + String get language; + + /// No description provided for @home. + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// No description provided for @settings. + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// No description provided for @update_profile. + /// + /// In en, this message translates to: + /// **'Update profile'** + String get update_profile; + + /// No description provided for @about. + /// + /// In en, this message translates to: + /// **'About'** + String get about; + + /// No description provided for @log_out. + /// + /// In en, this message translates to: + /// **'Log out'** + String get log_out; + + /// No description provided for @french. + /// + /// In en, this message translates to: + /// **'French'** + String get french; + + /// No description provided for @english. + /// + /// In en, this message translates to: + /// **'English'** + String get english; + + /// No description provided for @german. + /// + /// In en, this message translates to: + /// **'German'** + String get german; + + /// No description provided for @select_language. + /// + /// In en, this message translates to: + /// **'Select language'** + String get select_language; + + /// No description provided for @search_item. + /// + /// In en, this message translates to: + /// **'Search by item'** + String get search_item; + + /// No description provided for @search_tag. + /// + /// In en, this message translates to: + /// **'Search by tags'** + String get search_tag; + + /// No description provided for @search_geographical. + /// + /// In en, this message translates to: + /// **'Search by geographical zone'** + String get search_geographical; + + /// No description provided for @show_date_field. + /// + /// In en, this message translates to: + /// **'Show Date Fields'** + String get show_date_field; + + /// No description provided for @hide_date_field. + /// + /// In en, this message translates to: + /// **'Hide Date Fields'** + String get hide_date_field; + + /// No description provided for @no_data. + /// + /// In en, this message translates to: + /// **'No data available'** + String get no_data; + + /// No description provided for @search. + /// + /// In en, this message translates to: + /// **'Search'** + String get search; + + /// No description provided for @no_events. + /// + /// In en, this message translates to: + /// **'No events available for this location.'** + String get no_events; + + /// No description provided for @start_date. + /// + /// In en, this message translates to: + /// **'Start date'** + String get start_date; + + /// No description provided for @end_date. + /// + /// In en, this message translates to: + /// **'End date'** + String get end_date; + + /// No description provided for @failed_suggestions. + /// + /// In en, this message translates to: + /// **'Failed to load suggestions'** + String get failed_suggestions; + + /// No description provided for @error. + /// + /// In en, this message translates to: + /// **'Error'** + String get error; + + /// No description provided for @password_different. + /// + /// In en, this message translates to: + /// **'Must write a different password'** + String get password_different; + + /// No description provided for @create. + /// + /// In en, this message translates to: + /// **'Creation'** + String get create; + + /// No description provided for @user_create. + /// + /// In en, this message translates to: + /// **'Your user created'** + String get user_create; + + /// No description provided for @user_update. + /// + /// In en, this message translates to: + /// **'Your user updated'** + String get user_update; + + /// No description provided for @request_error. + /// + /// In en, this message translates to: + /// **'Poorly constructed query'** + String get request_error; + + /// No description provided for @incorrect_password. + /// + /// In en, this message translates to: + /// **'Incorrect password'** + String get incorrect_password; + + /// No description provided for @unknown_user. + /// + /// In en, this message translates to: + /// **'Unknown user'** + String get unknown_user; + + /// No description provided for @disabled_user. + /// + /// In en, this message translates to: + /// **'User disabled'** + String get disabled_user; + + /// No description provided for @invalid_token. + /// + /// In en, this message translates to: + /// **'Invalid token'** + String get invalid_token; + + /// No description provided for @internal_error_server. + /// + /// In en, this message translates to: + /// **'Internal error server'** + String get internal_error_server; + + /// No description provided for @unknown_error_auth. + /// + /// In en, this message translates to: + /// **'Unknown error authentification'** + String get unknown_error_auth; + + /// No description provided for @required_input. + /// + /// In en, this message translates to: + /// **'Required input'** + String get required_input; + + /// No description provided for @create_profile. + /// + /// In en, this message translates to: + /// **'Create profile'** + String get create_profile; + + /// No description provided for @edit_pseudo. + /// + /// In en, this message translates to: + /// **'Edit pseudo'** + String get edit_pseudo; + + /// No description provided for @password. + /// + /// In en, this message translates to: + /// **'Password'** + String get password; + + /// No description provided for @enter_password. + /// + /// In en, this message translates to: + /// **'Enter the passord'** + String get enter_password; + + /// No description provided for @password_confirmed. + /// + /// In en, this message translates to: + /// **'Password confirmed'** + String get password_confirmed; + + /// No description provided for @last_name. + /// + /// In en, this message translates to: + /// **'Last name'** + String get last_name; + + /// No description provided for @first_name. + /// + /// In en, this message translates to: + /// **'First name'** + String get first_name; + + /// No description provided for @email. + /// + /// In en, this message translates to: + /// **'Mail'** + String get email; + + /// No description provided for @edit_last_name. + /// + /// In en, this message translates to: + /// **'Edit name'** + String get edit_last_name; + + /// No description provided for @edit_first_name. + /// + /// In en, this message translates to: + /// **'Edit first name'** + String get edit_first_name; + + /// No description provided for @edit_email. + /// + /// In en, this message translates to: + /// **'Edit email address'** + String get edit_email; + + /// No description provided for @birth_date. + /// + /// In en, this message translates to: + /// **'Birth date'** + String get birth_date; + + /// No description provided for @edit_birth. + /// + /// In en, this message translates to: + /// **'Edit birth date'** + String get edit_birth; + + /// No description provided for @create_profile_button. + /// + /// In en, this message translates to: + /// **'Create profile'** + String get create_profile_button; + + /// No description provided for @take_picture. + /// + /// In en, this message translates to: + /// **'Take a picture'** + String get take_picture; + + /// No description provided for @error_ia. + /// + /// In en, this message translates to: + /// **'Google AI failed to analyze picture. Retry with another one'** + String get error_ia; + + /// No description provided for @no_data_geo. + /// + /// In en, this message translates to: + /// **'No geographical data'** + String get no_data_geo; + + /// No description provided for @response_status_update. + /// + /// In en, this message translates to: + /// **'response status code update'** + String get response_status_update; + + /// No description provided for @error_token. + /// + /// In en, this message translates to: + /// **'Token error'** + String get error_token; + + /// No description provided for @error_format. + /// + /// In en, this message translates to: + /// **'Data format error given by AI'** + String get error_format; + + /// No description provided for @display_picture. + /// + /// In en, this message translates to: + /// **'Display the Picture'** + String get display_picture; + + /// No description provided for @analyze_image. + /// + /// In en, this message translates to: + /// **'Image Analyze in progress'** + String get analyze_image; + + /// No description provided for @loading_progress. + /// + /// In en, this message translates to: + /// **'Loading progress'** + String get loading_progress; + + /// No description provided for @error_event. + /// + /// In en, this message translates to: + /// **'Event error'** + String get error_event; + + /// No description provided for @no_future_event. + /// + /// In en, this message translates to: + /// **'No future event'** + String get no_future_event; + + /// No description provided for @error_user. + /// + /// In en, this message translates to: + /// **'Error user'** + String get error_user; + + /// No description provided for @empty_input. + /// + /// In en, this message translates to: + /// **'Empty input'** + String get empty_input; + + /// No description provided for @info_event. + /// + /// In en, this message translates to: + /// **'Event info'** + String get info_event; + + /// No description provided for @event_already. + /// + /// In en, this message translates to: + /// **'Event already exists'** + String get event_already; + + /// No description provided for @picture_error. + /// + /// In en, this message translates to: + /// **'Picture error'** + String get picture_error; + + /// No description provided for @no_picture_published. + /// + /// In en, this message translates to: + /// **'No picture published'** + String get no_picture_published; + + /// No description provided for @event_update. + /// + /// In en, this message translates to: + /// **'Event updated'** + String get event_update; + + /// No description provided for @location. + /// + /// In en, this message translates to: + /// **'Location'** + String get location; + + /// No description provided for @add_event. + /// + /// In en, this message translates to: + /// **'Add or Update a event'** + String get add_event; + + /// No description provided for @edit_image. + /// + /// In en, this message translates to: + /// **'Edit pictures'** + String get edit_image; + + /// No description provided for @name. + /// + /// In en, this message translates to: + /// **'Name'** + String get name; + + /// No description provided for @edit_event_name. + /// + /// In en, this message translates to: + /// **'Edit event name'** + String get edit_event_name; + + /// No description provided for @start_time. + /// + /// In en, this message translates to: + /// **'Start time'** + String get start_time; + + /// No description provided for @end_time. + /// + /// In en, this message translates to: + /// **'End time'** + String get end_time; + + /// No description provided for @select_date. + /// + /// In en, this message translates to: + /// **'Click to select a date'** + String get select_date; + + /// No description provided for @select_time. + /// + /// In en, this message translates to: + /// **'Click to select a time'** + String get select_time; + + /// No description provided for @tag. + /// + /// In en, this message translates to: + /// **'Tags'** + String get tag; + + /// No description provided for @already_tag. + /// + /// In en, this message translates to: + /// **'You have already this tags'** + String get already_tag; + + /// No description provided for @enter_tag. + /// + /// In en, this message translates to: + /// **'Enter a tag'** + String get enter_tag; + + /// No description provided for @organizer. + /// + /// In en, this message translates to: + /// **'Organizer'** + String get organizer; + + /// No description provided for @already_organiser. + /// + /// In en, this message translates to: + /// **'You have already a organizer'** + String get already_organiser; + + /// No description provided for @enter_organizer. + /// + /// In en, this message translates to: + /// **'Enter a organizer'** + String get enter_organizer; + + /// No description provided for @description. + /// + /// In en, this message translates to: + /// **'Description'** + String get description; + + /// No description provided for @describe_event. + /// + /// In en, this message translates to: + /// **'Describe event'** + String get describe_event; + + /// No description provided for @add. + /// + /// In en, this message translates to: + /// **'Add'** + String get add; + + /// No description provided for @different_password_error. + /// + /// In en, this message translates to: + /// **'Different password'** + String get different_password_error; + + /// No description provided for @update. + /// + /// In en, this message translates to: + /// **'Update'** + String get update; + + /// No description provided for @updated. + /// + /// In en, this message translates to: + /// **'Updated'** + String get updated; + + /// No description provided for @settings_updated. + /// + /// In en, this message translates to: + /// **'Settings updated'** + String get settings_updated; + + /// No description provided for @define_kilometer. + /// + /// In en, this message translates to: + /// **'Define Kilometer'** + String get define_kilometer; + + /// No description provided for @email_sent. + /// + /// In en, this message translates to: + /// **'Email has been sent'** + String get email_sent; + + /// No description provided for @forgot_password. + /// + /// In en, this message translates to: + /// **'Forgot password'** + String get forgot_password; + + /// No description provided for @enter_email. + /// + /// In en, this message translates to: + /// **'Enter the email'** + String get enter_email; + + /// No description provided for @send_email. + /// + /// In en, this message translates to: + /// **'Send email'** + String get send_email; + + /// No description provided for @invalid_cache. + /// + /// In en, this message translates to: + /// **'Invalid cache'** + String get invalid_cache; + + /// No description provided for @item_date. + /// + /// In en, this message translates to: + /// **'Date : '** + String get item_date; + + /// No description provided for @item_maps. + /// + /// In en, this message translates to: + /// **'Maps : '** + String get item_maps; + + /// No description provided for @item_organizer. + /// + /// In en, this message translates to: + /// **'Organizer : '** + String get item_organizer; + + /// No description provided for @item_description. + /// + /// In en, this message translates to: + /// **'Description : '** + String get item_description; + + /// No description provided for @item_tags. + /// + /// In en, this message translates to: + /// **'Tags : '** + String get item_tags; + + /// No description provided for @failed_auth. + /// + /// In en, this message translates to: + /// **'Authentification failed'** + String get failed_auth; + + /// No description provided for @login_page. + /// + /// In en, this message translates to: + /// **'Login page'** + String get login_page; + + /// No description provided for @pseudo. + /// + /// In en, this message translates to: + /// **'Pseudo'** + String get pseudo; + + /// No description provided for @enter_existing_pseudo. + /// + /// In en, this message translates to: + /// **'Enter a existing pseudo'** + String get enter_existing_pseudo; + + /// No description provided for @remembr_me. + /// + /// In en, this message translates to: + /// **'Remember me'** + String get remembr_me; + + /// No description provided for @new_user. + /// + /// In en, this message translates to: + /// **'New User? Create Account'** + String get new_user; + + /// No description provided for @sign_in. + /// + /// In en, this message translates to: + /// **'Sign in'** + String get sign_in; + + /// No description provided for @map_token. + /// + /// In en, this message translates to: + /// **'Mapbox Access Token is not available'** + String get map_token; + + /// No description provided for @geo_disabled. + /// + /// In en, this message translates to: + /// **'Location services are disabled.'** + String get geo_disabled; + + /// No description provided for @permission_denied. + /// + /// In en, this message translates to: + /// **'Location permissions are denied.'** + String get permission_denied; + + /// No description provided for @enable_permission. + /// + /// In en, this message translates to: + /// **'Location permissions are permanently denied. Enable them in settings.'** + String get enable_permission; + + /// No description provided for @no_last_position. + /// + /// In en, this message translates to: + /// **'No last known position available.'** + String get no_last_position; + + /// No description provided for @failed_location. + /// + /// In en, this message translates to: + /// **'Failed to get user location'** + String get failed_location; + + /// No description provided for @failed_fetch. + /// + /// In en, this message translates to: + /// **'Failed to fetch the route'** + String get failed_fetch; + + /// No description provided for @invalid_coordinates_symbol. + /// + /// In en, this message translates to: + /// **'Invalid coordinates, cannot add symbol.'** + String get invalid_coordinates_symbol; + + /// No description provided for @error_symbol. + /// + /// In en, this message translates to: + /// **'Error when adding symbol.'** + String get error_symbol; + + /// No description provided for @position_not_init. + /// + /// In en, this message translates to: + /// **'User position is not yet initialized. Try again.'** + String get position_not_init; + + /// No description provided for @invalid_coordinates. + /// + /// In en, this message translates to: + /// **'Invalid coordinates.'** + String get invalid_coordinates; + + /// No description provided for @walking. + /// + /// In en, this message translates to: + /// **'Walking'** + String get walking; + + /// No description provided for @cycling. + /// + /// In en, this message translates to: + /// **'Cycling'** + String get cycling; + + /// No description provided for @driving. + /// + /// In en, this message translates to: + /// **'Driving'** + String get driving; + + /// No description provided for @get_direction. + /// + /// In en, this message translates to: + /// **'Get Directions and Markers'** + String get get_direction; + + /// No description provided for @missing_token. + /// + /// In en, this message translates to: + /// **'Missing access token'** + String get missing_token; + + /// No description provided for @geocoding_error. + /// + /// In en, this message translates to: + /// **'Error when geocoding'** + String get geocoding_error; + + /// No description provided for @no_found_place. + /// + /// In en, this message translates to: + /// **'No found place'** + String get no_found_place; + + /// No description provided for @upload_error. + /// + /// In en, this message translates to: + /// **'Error when image uploading'** + String get upload_error; + + /// No description provided for @event_added. + /// + /// In en, this message translates to: + /// **'Event added'** + String get event_added; + + /// No description provided for @unknown_error. + /// + /// In en, this message translates to: + /// **'Unknown error'** + String get unknown_error; + + /// No description provided for @app_error. + /// + /// In en, this message translates to: + /// **'Application error'** + String get app_error; + + /// No description provided for @at. + /// + /// In en, this message translates to: + /// **'at'** + String get at; + + /// No description provided for @to_date. + /// + /// In en, this message translates to: + /// **'to'** + String get to_date; + + /// No description provided for @item_link. + /// + /// In en, this message translates to: + /// **'Link : '** + String get item_link; + + /// No description provided for @item_ticket. + /// + /// In en, this message translates to: + /// **'Ticket : '** + String get item_ticket; + + /// No description provided for @link. + /// + /// In en, this message translates to: + /// **'Link'** + String get link; + + /// No description provided for @edit_link. + /// + /// In en, this message translates to: + /// **'Edit link name'** + String get edit_link; + + /// No description provided for @ticket. + /// + /// In en, this message translates to: + /// **'Ticket'** + String get ticket; + + /// No description provided for @edit_ticket. + /// + /// In en, this message translates to: + /// **'Edit ticket link'** + String get edit_ticket; + + /// No description provided for @toogle_interest. + /// + /// In en, this message translates to: + /// **'Error toggle interest'** + String get toogle_interest; + + /// No description provided for @error_update. + /// + /// In en, this message translates to: + /// **'Error when updating'** + String get error_update; + + /// No description provided for @count_interested. + /// + /// In en, this message translates to: + /// **'Interested people number'** + String get count_interested; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['de', 'en', 'fr'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'de': + return AppLocalizationsDe(); + case 'en': + return AppLocalizationsEn(); + case 'fr': + return AppLocalizationsFr(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); +} diff --git a/covas_mobile_new/lib/l10n/app_localizations_de.dart b/covas_mobile_new/lib/l10n/app_localizations_de.dart new file mode 100644 index 0000000..46d179a --- /dev/null +++ b/covas_mobile_new/lib/l10n/app_localizations_de.dart @@ -0,0 +1,434 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for German (`de`). +class AppLocalizationsDe extends AppLocalizations { + AppLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get menu_list => 'Veranstaltungsmenü'; + + @override + String get language => 'Sprache'; + + @override + String get home => 'Startseite'; + + @override + String get settings => 'Einstellungen'; + + @override + String get update_profile => 'Profil aktualisieren'; + + @override + String get about => 'Über'; + + @override + String get log_out => 'Abmelden'; + + @override + String get french => 'Französisch'; + + @override + String get english => 'Englisch'; + + @override + String get german => 'Deutsch'; + + @override + String get select_language => 'Sprache auswählen'; + + @override + String get search_item => 'Nach Element suchen'; + + @override + String get search_tag => 'Nach Schlagwörtern suchen'; + + @override + String get search_geographical => 'Nach geografischer Zone suchen'; + + @override + String get show_date_field => 'Datumsfelder anzeigen'; + + @override + String get hide_date_field => 'Datumsfelder ausblenden'; + + @override + String get no_data => 'Keine Daten verfügbar'; + + @override + String get search => 'Suchen'; + + @override + String get no_events => 'Keine Veranstaltungen für diesen Ort verfügbar.'; + + @override + String get start_date => 'Anfangsdatum'; + + @override + String get end_date => 'Enddatum'; + + @override + String get failed_suggestions => 'Vorschläge konnten nicht geladen werden'; + + @override + String get error => 'Fehler'; + + @override + String get password_different => 'Ein anderes Passwort eingeben'; + + @override + String get create => 'Erstellung'; + + @override + String get user_create => 'Benutzer wurde erstellt'; + + @override + String get user_update => 'Benutzer wurde aktualisiert'; + + @override + String get request_error => 'Fehlerhafte Anfrage'; + + @override + String get incorrect_password => 'Falsches Passwort'; + + @override + String get unknown_user => 'Unbekannter Benutzer'; + + @override + String get disabled_user => 'Benutzer deaktiviert'; + + @override + String get invalid_token => 'Ungültiger Token'; + + @override + String get internal_error_server => 'Interner Serverfehler'; + + @override + String get unknown_error_auth => 'Unbekannter Authentifizierungsfehler'; + + @override + String get required_input => 'Pflichtfeld'; + + @override + String get create_profile => 'Profil erstellen'; + + @override + String get edit_pseudo => 'Benutzernamen bearbeiten'; + + @override + String get password => 'Passwort'; + + @override + String get enter_password => 'Passwort eingeben'; + + @override + String get password_confirmed => 'Passwort bestätigt'; + + @override + String get last_name => 'Nachname'; + + @override + String get first_name => 'Vorname'; + + @override + String get email => 'E-Mail'; + + @override + String get edit_last_name => 'Nachnamen bearbeiten'; + + @override + String get edit_first_name => 'Vornamen bearbeiten'; + + @override + String get edit_email => 'E-Mail-Adresse bearbeiten'; + + @override + String get birth_date => 'Geburtsdatum'; + + @override + String get edit_birth => 'Geburtsdatum bearbeiten'; + + @override + String get create_profile_button => 'Profil erstellen'; + + @override + String get take_picture => 'Foto aufnehmen'; + + @override + String get error_ia => + 'Google KI konnte das Bild nicht analysieren. Bitte ein anderes versuchen.'; + + @override + String get no_data_geo => 'Keine geografischen Daten'; + + @override + String get response_status_update => 'Statuscode-Antwort aktualisieren'; + + @override + String get error_token => 'Token-Fehler'; + + @override + String get error_format => 'Vom KI geliefertes Datenformat ist fehlerhaft'; + + @override + String get display_picture => 'Bild anzeigen'; + + @override + String get analyze_image => 'Bildanalyse läuft'; + + @override + String get loading_progress => 'Ladefortschritt'; + + @override + String get error_event => 'Veranstaltungsfehler'; + + @override + String get no_future_event => 'Keine zukünftigen Veranstaltungen'; + + @override + String get error_user => 'Benutzerfehler'; + + @override + String get empty_input => 'Eingabefeld leer'; + + @override + String get info_event => 'Veranstaltungsinfo'; + + @override + String get event_already => 'Veranstaltung existiert bereits'; + + @override + String get picture_error => 'Bildfehler'; + + @override + String get no_picture_published => 'Kein Bild veröffentlicht'; + + @override + String get event_update => 'Veranstaltung aktualisiert'; + + @override + String get location => 'Ort'; + + @override + String get add_event => 'Veranstaltung hinzufügen oder aktualisieren'; + + @override + String get edit_image => 'Bilder bearbeiten'; + + @override + String get name => 'Name'; + + @override + String get edit_event_name => 'Veranstaltungsname bearbeiten'; + + @override + String get start_time => 'Startzeit'; + + @override + String get end_time => 'Endzeit'; + + @override + String get select_date => 'Zum Auswählen eines Datums klicken'; + + @override + String get select_time => 'Zum Auswählen einer Uhrzeit klicken'; + + @override + String get tag => 'Schlagwörter'; + + @override + String get already_tag => 'Dieses Schlagwort ist bereits vorhanden'; + + @override + String get enter_tag => 'Ein Schlagwort eingeben'; + + @override + String get organizer => 'Veranstalter'; + + @override + String get already_organiser => 'Veranstalter bereits vorhanden'; + + @override + String get enter_organizer => 'Veranstalter eingeben'; + + @override + String get description => 'Beschreibung'; + + @override + String get describe_event => 'Veranstaltung beschreiben'; + + @override + String get add => 'Hinzufügen'; + + @override + String get different_password_error => 'Passwörter stimmen nicht überein'; + + @override + String get update => 'Aktualisieren'; + + @override + String get updated => 'Aktualisiert'; + + @override + String get settings_updated => 'Einstellungen aktualisiert'; + + @override + String get define_kilometer => 'Kilometer definieren'; + + @override + String get email_sent => 'E-Mail wurde gesendet'; + + @override + String get forgot_password => 'Passwort vergessen'; + + @override + String get enter_email => 'E-Mail eingeben'; + + @override + String get send_email => 'E-Mail senden'; + + @override + String get invalid_cache => 'Ungültiger Cache'; + + @override + String get item_date => 'Datum : '; + + @override + String get item_maps => 'Karte : '; + + @override + String get item_organizer => 'Veranstalter : '; + + @override + String get item_description => 'Beschreibung : '; + + @override + String get item_tags => 'Schlagwörter : '; + + @override + String get failed_auth => 'Authentifizierung fehlgeschlagen'; + + @override + String get login_page => 'Anmeldeseite'; + + @override + String get pseudo => 'Benutzername'; + + @override + String get enter_existing_pseudo => 'Vorhandenen Benutzernamen eingeben'; + + @override + String get remembr_me => 'Angemeldet bleiben'; + + @override + String get new_user => 'Neuer Benutzer? Konto erstellen'; + + @override + String get sign_in => 'Anmelden'; + + @override + String get map_token => 'Mapbox-Zugangstoken ist nicht verfügbar'; + + @override + String get geo_disabled => 'Standortdienste sind deaktiviert.'; + + @override + String get permission_denied => 'Standortberechtigungen wurden verweigert.'; + + @override + String get enable_permission => + 'Standortberechtigungen dauerhaft verweigert. Bitte in den Einstellungen aktivieren.'; + + @override + String get no_last_position => 'Keine letzte bekannte Position verfügbar.'; + + @override + String get failed_location => 'Standort konnte nicht ermittelt werden'; + + @override + String get failed_fetch => 'Route konnte nicht abgerufen werden'; + + @override + String get invalid_coordinates_symbol => + 'Ungültige Koordinaten, Symbol kann nicht hinzugefügt werden.'; + + @override + String get error_symbol => 'Fehler beim Hinzufügen des Symbols.'; + + @override + String get position_not_init => + 'Benutzerposition noch nicht initialisiert. Erneut versuchen.'; + + @override + String get invalid_coordinates => 'Ungültige Koordinaten.'; + + @override + String get walking => 'Zu Fuß'; + + @override + String get cycling => 'Mit dem Fahrrad'; + + @override + String get driving => 'Mit dem Auto'; + + @override + String get get_direction => 'Wegbeschreibung und Markierungen anzeigen'; + + @override + String get missing_token => 'Zugangstoken fehlt'; + + @override + String get geocoding_error => 'Fehler bei der Geokodierung'; + + @override + String get no_found_place => 'Kein Ort gefunden'; + + @override + String get upload_error => 'Fehler beim Hochladen des Bildes'; + + @override + String get event_added => 'Veranstaltung hinzugefügt'; + + @override + String get unknown_error => 'Unbekannter Fehler'; + + @override + String get app_error => 'Anwendungsfehler'; + + @override + String get at => 'um'; + + @override + String get to_date => 'bis'; + + @override + String get item_link => 'Link : '; + + @override + String get item_ticket => 'Abendkarte : '; + + @override + String get link => 'Link'; + + @override + String get edit_link => 'Linkereignis bearbeiten'; + + @override + String get ticket => 'Abendkarte'; + + @override + String get edit_ticket => 'Ticketlink bearbeiten'; + + @override + String get toogle_interest => 'Fehler beim Umschalten des Interesses'; + + @override + String get error_update => 'Fehler beim Update'; + + @override + String get count_interested => 'Anzahl der Interessenten'; +} diff --git a/covas_mobile_new/lib/l10n/app_localizations_en.dart b/covas_mobile_new/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..59d7a22 --- /dev/null +++ b/covas_mobile_new/lib/l10n/app_localizations_en.dart @@ -0,0 +1,434 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get menu_list => 'Event list menu'; + + @override + String get language => 'Language'; + + @override + String get home => 'Home'; + + @override + String get settings => 'Settings'; + + @override + String get update_profile => 'Update profile'; + + @override + String get about => 'About'; + + @override + String get log_out => 'Log out'; + + @override + String get french => 'French'; + + @override + String get english => 'English'; + + @override + String get german => 'German'; + + @override + String get select_language => 'Select language'; + + @override + String get search_item => 'Search by item'; + + @override + String get search_tag => 'Search by tags'; + + @override + String get search_geographical => 'Search by geographical zone'; + + @override + String get show_date_field => 'Show Date Fields'; + + @override + String get hide_date_field => 'Hide Date Fields'; + + @override + String get no_data => 'No data available'; + + @override + String get search => 'Search'; + + @override + String get no_events => 'No events available for this location.'; + + @override + String get start_date => 'Start date'; + + @override + String get end_date => 'End date'; + + @override + String get failed_suggestions => 'Failed to load suggestions'; + + @override + String get error => 'Error'; + + @override + String get password_different => 'Must write a different password'; + + @override + String get create => 'Creation'; + + @override + String get user_create => 'Your user created'; + + @override + String get user_update => 'Your user updated'; + + @override + String get request_error => 'Poorly constructed query'; + + @override + String get incorrect_password => 'Incorrect password'; + + @override + String get unknown_user => 'Unknown user'; + + @override + String get disabled_user => 'User disabled'; + + @override + String get invalid_token => 'Invalid token'; + + @override + String get internal_error_server => 'Internal error server'; + + @override + String get unknown_error_auth => 'Unknown error authentification'; + + @override + String get required_input => 'Required input'; + + @override + String get create_profile => 'Create profile'; + + @override + String get edit_pseudo => 'Edit pseudo'; + + @override + String get password => 'Password'; + + @override + String get enter_password => 'Enter the passord'; + + @override + String get password_confirmed => 'Password confirmed'; + + @override + String get last_name => 'Last name'; + + @override + String get first_name => 'First name'; + + @override + String get email => 'Mail'; + + @override + String get edit_last_name => 'Edit name'; + + @override + String get edit_first_name => 'Edit first name'; + + @override + String get edit_email => 'Edit email address'; + + @override + String get birth_date => 'Birth date'; + + @override + String get edit_birth => 'Edit birth date'; + + @override + String get create_profile_button => 'Create profile'; + + @override + String get take_picture => 'Take a picture'; + + @override + String get error_ia => + 'Google AI failed to analyze picture. Retry with another one'; + + @override + String get no_data_geo => 'No geographical data'; + + @override + String get response_status_update => 'response status code update'; + + @override + String get error_token => 'Token error'; + + @override + String get error_format => 'Data format error given by AI'; + + @override + String get display_picture => 'Display the Picture'; + + @override + String get analyze_image => 'Image Analyze in progress'; + + @override + String get loading_progress => 'Loading progress'; + + @override + String get error_event => 'Event error'; + + @override + String get no_future_event => 'No future event'; + + @override + String get error_user => 'Error user'; + + @override + String get empty_input => 'Empty input'; + + @override + String get info_event => 'Event info'; + + @override + String get event_already => 'Event already exists'; + + @override + String get picture_error => 'Picture error'; + + @override + String get no_picture_published => 'No picture published'; + + @override + String get event_update => 'Event updated'; + + @override + String get location => 'Location'; + + @override + String get add_event => 'Add or Update a event'; + + @override + String get edit_image => 'Edit pictures'; + + @override + String get name => 'Name'; + + @override + String get edit_event_name => 'Edit event name'; + + @override + String get start_time => 'Start time'; + + @override + String get end_time => 'End time'; + + @override + String get select_date => 'Click to select a date'; + + @override + String get select_time => 'Click to select a time'; + + @override + String get tag => 'Tags'; + + @override + String get already_tag => 'You have already this tags'; + + @override + String get enter_tag => 'Enter a tag'; + + @override + String get organizer => 'Organizer'; + + @override + String get already_organiser => 'You have already a organizer'; + + @override + String get enter_organizer => 'Enter a organizer'; + + @override + String get description => 'Description'; + + @override + String get describe_event => 'Describe event'; + + @override + String get add => 'Add'; + + @override + String get different_password_error => 'Different password'; + + @override + String get update => 'Update'; + + @override + String get updated => 'Updated'; + + @override + String get settings_updated => 'Settings updated'; + + @override + String get define_kilometer => 'Define Kilometer'; + + @override + String get email_sent => 'Email has been sent'; + + @override + String get forgot_password => 'Forgot password'; + + @override + String get enter_email => 'Enter the email'; + + @override + String get send_email => 'Send email'; + + @override + String get invalid_cache => 'Invalid cache'; + + @override + String get item_date => 'Date : '; + + @override + String get item_maps => 'Maps : '; + + @override + String get item_organizer => 'Organizer : '; + + @override + String get item_description => 'Description : '; + + @override + String get item_tags => 'Tags : '; + + @override + String get failed_auth => 'Authentification failed'; + + @override + String get login_page => 'Login page'; + + @override + String get pseudo => 'Pseudo'; + + @override + String get enter_existing_pseudo => 'Enter a existing pseudo'; + + @override + String get remembr_me => 'Remember me'; + + @override + String get new_user => 'New User? Create Account'; + + @override + String get sign_in => 'Sign in'; + + @override + String get map_token => 'Mapbox Access Token is not available'; + + @override + String get geo_disabled => 'Location services are disabled.'; + + @override + String get permission_denied => 'Location permissions are denied.'; + + @override + String get enable_permission => + 'Location permissions are permanently denied. Enable them in settings.'; + + @override + String get no_last_position => 'No last known position available.'; + + @override + String get failed_location => 'Failed to get user location'; + + @override + String get failed_fetch => 'Failed to fetch the route'; + + @override + String get invalid_coordinates_symbol => + 'Invalid coordinates, cannot add symbol.'; + + @override + String get error_symbol => 'Error when adding symbol.'; + + @override + String get position_not_init => + 'User position is not yet initialized. Try again.'; + + @override + String get invalid_coordinates => 'Invalid coordinates.'; + + @override + String get walking => 'Walking'; + + @override + String get cycling => 'Cycling'; + + @override + String get driving => 'Driving'; + + @override + String get get_direction => 'Get Directions and Markers'; + + @override + String get missing_token => 'Missing access token'; + + @override + String get geocoding_error => 'Error when geocoding'; + + @override + String get no_found_place => 'No found place'; + + @override + String get upload_error => 'Error when image uploading'; + + @override + String get event_added => 'Event added'; + + @override + String get unknown_error => 'Unknown error'; + + @override + String get app_error => 'Application error'; + + @override + String get at => 'at'; + + @override + String get to_date => 'to'; + + @override + String get item_link => 'Link : '; + + @override + String get item_ticket => 'Ticket : '; + + @override + String get link => 'Link'; + + @override + String get edit_link => 'Edit link name'; + + @override + String get ticket => 'Ticket'; + + @override + String get edit_ticket => 'Edit ticket link'; + + @override + String get toogle_interest => 'Error toggle interest'; + + @override + String get error_update => 'Error when updating'; + + @override + String get count_interested => 'Interested people number'; +} diff --git a/covas_mobile_new/lib/l10n/app_localizations_fr.dart b/covas_mobile_new/lib/l10n/app_localizations_fr.dart new file mode 100644 index 0000000..8f1750b --- /dev/null +++ b/covas_mobile_new/lib/l10n/app_localizations_fr.dart @@ -0,0 +1,437 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for French (`fr`). +class AppLocalizationsFr extends AppLocalizations { + AppLocalizationsFr([String locale = 'fr']) : super(locale); + + @override + String get menu_list => 'Liste d\'évènement'; + + @override + String get language => 'Langue'; + + @override + String get home => 'Accueil'; + + @override + String get settings => 'Paramètres'; + + @override + String get update_profile => 'Modifier le profil'; + + @override + String get about => 'À propos'; + + @override + String get log_out => 'Se déconnecter'; + + @override + String get french => 'Français'; + + @override + String get english => 'Anglais'; + + @override + String get german => 'Allemand'; + + @override + String get select_language => 'Selectionne la langue'; + + @override + String get search_item => 'Recherche par item'; + + @override + String get search_tag => 'Recherche par tags'; + + @override + String get search_geographical => 'Recherche par zone géographique'; + + @override + String get show_date_field => 'Afficher champ date'; + + @override + String get hide_date_field => 'Cacher Date Fields'; + + @override + String get no_data => 'Aucune donnée disponible'; + + @override + String get search => 'Recherche'; + + @override + String get no_events => 'Pas d\'évènements dans cette localisation'; + + @override + String get start_date => 'Date de début'; + + @override + String get end_date => 'Date de fin'; + + @override + String get failed_suggestions => 'Echec de chargement des suggestions'; + + @override + String get error => 'Erreur'; + + @override + String get password_different => 'Tu dois écrire un mot de passe different'; + + @override + String get create => 'Création'; + + @override + String get user_create => 'Votre utilisateur a été créé'; + + @override + String get user_update => 'Votre utilisateur a été modifié'; + + @override + String get request_error => 'Requête mal construite'; + + @override + String get incorrect_password => 'Mot de passe incorrect'; + + @override + String get unknown_user => 'Utilisateur inconnu'; + + @override + String get disabled_user => 'Utilisateur désactivé'; + + @override + String get invalid_token => 'Token invalide'; + + @override + String get internal_error_server => 'Erreur interne de serveur'; + + @override + String get unknown_error_auth => 'Problème d\'authentification inconnu'; + + @override + String get required_input => 'Champ requis'; + + @override + String get create_profile => 'Creation profil'; + + @override + String get edit_pseudo => 'Modifier le pseudo'; + + @override + String get password => 'Mot de passe'; + + @override + String get enter_password => 'Entrez le password'; + + @override + String get password_confirmed => 'Confirmez le mot de passe'; + + @override + String get last_name => 'Nom'; + + @override + String get first_name => 'Prénom'; + + @override + String get email => 'Email'; + + @override + String get edit_last_name => 'Modifier le nom'; + + @override + String get edit_first_name => 'Modifier le prénom'; + + @override + String get edit_email => 'Modifier l\'email'; + + @override + String get birth_date => 'Date de naissance'; + + @override + String get edit_birth => 'Modifier la date de naissance'; + + @override + String get create_profile_button => 'Créer le profil'; + + @override + String get take_picture => 'Take a picture'; + + @override + String get error_ia => + 'L\'IA de Google n\'a pas su analyser l\'image. Recommencer avec une autre'; + + @override + String get no_data_geo => 'Aucune donnée géographique'; + + @override + String get response_status_update => + 'Code du statut de réponse de la modification'; + + @override + String get error_token => 'Erreur de token'; + + @override + String get error_format => 'Erreur de format de donnée fourni par l\'IA'; + + @override + String get display_picture => 'Display the Picture'; + + @override + String get analyze_image => 'Analyse de l\'image en cours'; + + @override + String get loading_progress => 'Chargement en cours'; + + @override + String get error_event => 'Erreur de l\'évènement'; + + @override + String get no_future_event => 'Évènement non futur'; + + @override + String get error_user => 'Erreur de l\'utilisateur'; + + @override + String get empty_input => 'Champ vide'; + + @override + String get info_event => 'Event info'; + + @override + String get event_already => 'Event already exists'; + + @override + String get picture_error => 'Erreur image'; + + @override + String get no_picture_published => 'Image non publiée'; + + @override + String get event_update => 'Évènement modifié'; + + @override + String get location => 'Lieu'; + + @override + String get add_event => 'Ajouter ou modifier un évènement'; + + @override + String get edit_image => 'Changer la photo'; + + @override + String get name => 'Nom'; + + @override + String get edit_event_name => 'Changer le nom de l\'évènement'; + + @override + String get start_time => 'Heure de début'; + + @override + String get end_time => 'Heure de fin'; + + @override + String get select_date => 'Cliquer pour selectionner une date'; + + @override + String get select_time => 'Cliquer pour selectionner une heure'; + + @override + String get tag => 'Tags'; + + @override + String get already_tag => 'Tu as déjà entré ce tag'; + + @override + String get enter_tag => 'Entrer un tag'; + + @override + String get organizer => 'Organisateur'; + + @override + String get already_organiser => 'Tu as déjà rentré cet organisateur'; + + @override + String get enter_organizer => 'Entrer un organisateur'; + + @override + String get description => 'Description'; + + @override + String get describe_event => 'Décrire l\'évènement'; + + @override + String get add => 'Ajouter'; + + @override + String get different_password_error => 'Mot de passe différent'; + + @override + String get update => 'Mettre à jour'; + + @override + String get updated => 'Mis à jour'; + + @override + String get settings_updated => 'Paramètre mis à jour'; + + @override + String get define_kilometer => 'Definir un kilomètre'; + + @override + String get email_sent => 'Email a été envoyé'; + + @override + String get forgot_password => 'Mot de passe oublié'; + + @override + String get enter_email => 'Entrez l\'email'; + + @override + String get send_email => 'Send email'; + + @override + String get invalid_cache => 'Cache invalide'; + + @override + String get item_date => 'Date : '; + + @override + String get item_maps => 'Carte : '; + + @override + String get item_organizer => 'Organisateurs : '; + + @override + String get item_description => 'Description : '; + + @override + String get item_tags => 'Tags : '; + + @override + String get failed_auth => 'Échec de l\'authenticaton'; + + @override + String get login_page => 'Page d\'authentification'; + + @override + String get pseudo => 'Pseudo'; + + @override + String get enter_existing_pseudo => 'Entrez un pseudo existant'; + + @override + String get remembr_me => 'Se souvenir de moi'; + + @override + String get new_user => 'Nouvel utilisateur ? Créer un compte'; + + @override + String get sign_in => 'Se connecter'; + + @override + String get map_token => 'Token d\'accès de Mapbox n\'est pas disponible'; + + @override + String get geo_disabled => 'Les services de localisation sont désactivés.'; + + @override + String get permission_denied => + 'Les permissions de localisation sont refusées.'; + + @override + String get enable_permission => + 'Les permissions de localisation sont toujours désactivés. Il faut les désactiver'; + + @override + String get no_last_position => 'Aucune position n\'est pas disponible.'; + + @override + String get failed_location => + 'Échec de récupération des données geographique'; + + @override + String get failed_fetch => 'Échec de récupération des routes'; + + @override + String get invalid_coordinates_symbol => + 'Coordonnées invalides. On ne peut pas ajouter le symbole'; + + @override + String get error_symbol => 'Erreur lors de l\'ajout du symbole'; + + @override + String get position_not_init => + 'Coordonnées non initialisées. Essaye encore.'; + + @override + String get invalid_coordinates => 'Coordonnées invalides'; + + @override + String get walking => 'Marche'; + + @override + String get cycling => 'Vélo'; + + @override + String get driving => 'Voiture'; + + @override + String get get_direction => 'Get Directions and Markers'; + + @override + String get missing_token => 'Token d\'accès manquant'; + + @override + String get geocoding_error => 'Erreur lors du geocodage'; + + @override + String get no_found_place => 'Lieu introuvable'; + + @override + String get upload_error => 'Erreur lors de l\'upload d\'image'; + + @override + String get event_added => 'Évènement ajouté'; + + @override + String get unknown_error => 'Erreur inconnue'; + + @override + String get app_error => 'Erreur d\'application'; + + @override + String get at => 'à'; + + @override + String get to_date => 'jusqu\'à'; + + @override + String get item_link => 'Lien : '; + + @override + String get item_ticket => 'Billet : '; + + @override + String get link => 'Lien'; + + @override + String get edit_link => 'Editer le lien'; + + @override + String get ticket => 'Billet'; + + @override + String get edit_ticket => 'Editer le lien du billet'; + + @override + String get toogle_interest => 'Erreur de bouton de changement'; + + @override + String get error_update => 'Erreur lors de la mise à jour'; + + @override + String get count_interested => 'Nombre de personne interessé'; +} diff --git a/covas_mobile_new/lib/locale_provider.dart b/covas_mobile_new/lib/locale_provider.dart new file mode 100644 index 0000000..0d90807 --- /dev/null +++ b/covas_mobile_new/lib/locale_provider.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class LocaleProvider with ChangeNotifier { + static const _localeKey = 'locale_code'; + + Locale _locale = const Locale('en'); + + Locale get locale => _locale; + + LocaleProvider() { + _loadLocale(); + } + + Future _loadLocale() async { + final prefs = await SharedPreferences.getInstance(); + final code = prefs.getString(_localeKey); + if (code != null && L10n.all.contains(Locale(code))) { + _locale = Locale(code); + notifyListeners(); + } + } + + void setLocale(Locale locale) async { + if (!L10n.all.contains(locale)) return; + + _locale = locale; + notifyListeners(); + + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_localeKey, locale.languageCode); + } +} + +class L10n { + static final all = [ + const Locale('en'), + const Locale('fr'), + const Locale('de') + ]; +} diff --git a/covas_mobile_new/lib/main.dart b/covas_mobile_new/lib/main.dart new file mode 100644 index 0000000..cba36b6 --- /dev/null +++ b/covas_mobile_new/lib/main.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:provider/provider.dart'; + +import 'pages/LoginDemo.dart'; +import 'locale_provider.dart'; // <-- à adapter selon ton arborescence +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'classes/notification_service.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + await NotificationService.initialize(); + + runApp( + ChangeNotifierProvider( + create: (_) => LocaleProvider(), + child: MyApp(), + ), + ); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + final localeProvider = Provider.of( + context); // écoute les changements de langue + return MaterialApp( + debugShowCheckedModeBanner: false, + locale: localeProvider.locale, // <-- utilise la locale courante + supportedLocales: L10n.all, + localizationsDelegates: AppLocalizations.localizationsDelegates, + home: LoginDemo(), + ); + } +} diff --git a/covas_mobile_new/lib/pages/AddProfile.dart b/covas_mobile_new/lib/pages/AddProfile.dart new file mode 100644 index 0000000..5888f57 --- /dev/null +++ b/covas_mobile_new/lib/pages/AddProfile.dart @@ -0,0 +1,335 @@ +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; + +import 'dart:convert'; +import 'dart:io'; + +import '../pages/LoginDemo.dart'; + +import '../classes/alert.dart'; + +import '../variable/globals.dart' as globals; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé plus loin + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: AddProfile(), + ); + } +} + +class AddProfile extends StatefulWidget { + const AddProfile({super.key}); + + @override + _AddProfileState createState() => _AddProfileState(); +} + +class _AddProfileState extends State with ShowAlertDialog { + BannerAd? _bannerAd; + + TextEditingController inputUserName = TextEditingController(); + + TextEditingController inputName = TextEditingController(); + + TextEditingController inputFirstName = TextEditingController(); + TextEditingController inputEmail = TextEditingController(); + TextEditingController inputBirth = TextEditingController(); + TextEditingController inputPassword = TextEditingController(); + TextEditingController inputPasswordConfirmed = TextEditingController(); + + onTapFunctionDatePicker({required BuildContext context}) async { + DateTime? pickedDate = await showDatePicker( + context: context, + firstDate: DateTime(1900), + initialDate: DateTime.now(), + lastDate: DateTime(2104)); + if (pickedDate == null) return; + inputBirth.text = DateFormat("dd/MM/yyyy").format(pickedDate); + } + + convertNulltoEmptyString(var check) { + if (check == null) { + return ""; + } + return check; + } + + convertNulltoArray(List check) { + if (check == null) { + return []; + } + return check; + } + + String formatDate(String date) { + var splitedDate = date.split("/"); + + var day = splitedDate[0]; + var month = splitedDate[1]; + var year = splitedDate[2]; + + return "${year}-${month}-${day}"; + } + + Future _createProfile(BuildContext context) async { + var username = inputUserName.text; + var firstName = inputFirstName.text; + var name = inputName.text; + var email = inputEmail.text; + var password = inputPassword.text; + var confirmedPassword = inputPasswordConfirmed.text; + var birth = DateTime.parse(formatDate(inputBirth.text)); + + if ((password.isNotEmpty) && (confirmedPassword.isNotEmpty)) { + if (password != confirmedPassword) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.password_different ?? + "Must write a different password"); + return; + } + } + + var urlPut = Uri.parse("${globals.api}/mail"); + + var responsePost = await http.post(urlPut, + headers: { + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json' + }, + body: jsonEncode({ + 'name': name, + 'username': username, + 'firstName': firstName, + 'password': password, + 'email': email, + 'birth': birth.toString() + })); + print(responsePost.statusCode); + if (responsePost.statusCode == 200) { + showAlertDialog( + context, + AppLocalizations.of(context)?.create ?? "Creation", + AppLocalizations.of(context)?.user_create ?? "Your user created"); + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (_) => LoginDemo())); + return; + } + + final errorMessages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + + final text = errorMessages[responsePost.statusCode] ?? + AppLocalizations.of(context)?.unknown_error_auth ?? + "Unknown error auth"; + showAlertDialog( + context, AppLocalizations.of(context)?.error ?? "Error", text); + } + + @override + void initState() { + super.initState(); + + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + } + + final _formKey = GlobalKey(); + String? _validateField(String? value) { + return value!.isEmpty + ? AppLocalizations.of(context)?.required_input ?? 'Required input' + : null; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: Text( + AppLocalizations.of(context)?.create_profile ?? "Create profile"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!)), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputUserName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: 'Pseudo', + hintText: AppLocalizations.of(context)?.edit_pseudo ?? + 'Edit pseudo'), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputPassword, + validator: (value) => _validateField(value), + obscureText: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.password ?? + 'Password', + hintText: + AppLocalizations.of(context)?.enter_password ?? + 'Enter the password'), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputPasswordConfirmed, + validator: (value) => _validateField(value), + obscureText: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.password_confirmed ?? + 'Password confirmed', + hintText: + AppLocalizations.of(context)?.password_confirmed ?? + 'Password confirmed', + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.last_name ?? + 'Last name', + hintText: + AppLocalizations.of(context)?.edit_last_name ?? + 'Edit last name'), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputFirstName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.first_name ?? + 'First name', + hintText: + AppLocalizations.of(context)?.edit_first_name ?? + 'Edit first name'), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputEmail, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.email ?? 'Email', + hintText: AppLocalizations.of(context)?.edit_email ?? + 'Edit email'), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputBirth, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.birth_date ?? + 'Birth date', + hintText: AppLocalizations.of(context)?.edit_birth ?? + 'Edit birth date'), + onTap: () => onTapFunctionDatePicker(context: context)), + ), + SizedBox( + height: 30, + ), + Container( + height: 50, + width: 250, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(20)), + child: TextButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + _createProfile(context); + } + }, + child: Text( + AppLocalizations.of(context)?.create_profile_button ?? + "Create profile", + style: TextStyle(color: Colors.white, fontSize: 25), + ), + ), + ) + ], + ), + ), + )); + } +} diff --git a/covas_mobile_new/lib/pages/Camera.dart b/covas_mobile_new/lib/pages/Camera.dart new file mode 100644 index 0000000..0189dc0 --- /dev/null +++ b/covas_mobile_new/lib/pages/Camera.dart @@ -0,0 +1,163 @@ +import 'dart:async'; + +import 'package:image_picker/image_picker.dart'; +import '../classes/MyDrawer.dart'; + +import 'DisplayPictureScreen.dart'; +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé plus loin + +Future main() async { + // Ensure that plugin services are initialized so that `availableCameras()` + // can be called before `runApp()` + WidgetsFlutterBinding.ensureInitialized(); + + // Obtain a list of the available cameras on the device. + final cameras = await availableCameras(); + + // Get a specific camera from the list of available cameras. + final firstCamera = cameras.first; + + runApp( + MaterialApp( + theme: ThemeData.dark(), + home: Camera( + // Pass the appropriate camera to the TakePictureScreen widget. + camera: firstCamera, + ), + ), + ); +} + +// A screen that allows users to take a picture using a given camera. +class Camera extends StatefulWidget { + const Camera({ + super.key, + required this.camera, + }); + + final CameraDescription camera; + + @override + CameraState createState() => CameraState(); +} + +class CameraState extends State { + late CameraController _controller; + late Future _initializeControllerFuture; + final AuthService _authService = AuthService(); + + @override + void initState() { + super.initState(); + // To display the current output from the Camera, + // create a CameraController. + _authService.checkTokenStatus(context); + + _controller = CameraController( + // Get a specific camera from the list of available cameras. + widget.camera, + // Define the resolution to use. + ResolutionPreset.medium, + ); + + // Next, initialize the controller. This returns a Future. + _initializeControllerFuture = _controller.initialize(); + } + + Future pickImage() async { + final imagePicker = ImagePicker(); + final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DisplayPictureScreen( + // Pass the automatically generated path to + // the DisplayPictureScreen widget. + imagePath: pickedFile.path, + ), + ), + ); + } + } + + @override + void dispose() { + // Dispose of the controller when the widget is disposed. + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.take_picture ?? + "Take a picture")), + // You must wait until the controller is initialized before displaying the + // camera preview. Use a FutureBuilder to display a loading spinner until the + // controller has finished initializing. + drawer: MyDrawer(), + body: FutureBuilder( + future: _initializeControllerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the Future is complete, display the preview. + return CameraPreview(_controller); + } else { + // Otherwise, display a loading indicator. + return const Center(child: CircularProgressIndicator()); + } + }, + ), + floatingActionButton: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FloatingActionButton( + onPressed: pickImage, + child: Icon(Icons.photo_library), + ), + SizedBox(width: 40), + FloatingActionButton( + // Provide an onPressed callback. + onPressed: () async { + // Take the Picture in a try / catch block. If anything goes wrong, + // catch the error. + try { + // Ensure that the camera is initialized. + await _initializeControllerFuture; + + // Attempt to take a picture and get the file `image` + // where it was saved. + final image = await _controller.takePicture(); + + if (!context.mounted) return; + + // If the picture was taken, display it on a new screen. + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => DisplayPictureScreen( + // Pass the automatically generated path to + // the DisplayPictureScreen widget. + imagePath: image.path, + ), + ), + ); + } catch (e) { + // If an error occurs, log the error to the console. + print(e); + } + }, + child: const Icon(Icons.camera_alt), + ) + ], + ))); + } +} diff --git a/covas_mobile_new/lib/pages/CameraEdit.dart b/covas_mobile_new/lib/pages/CameraEdit.dart new file mode 100644 index 0000000..408e8fc --- /dev/null +++ b/covas_mobile_new/lib/pages/CameraEdit.dart @@ -0,0 +1,166 @@ +import 'dart:async'; +import 'dart:io'; + +import '../classes/events.dart'; +import '../classes/MyDrawer.dart'; +import 'package:image_picker/image_picker.dart'; +import 'EditEvent.dart'; +import 'package:camera/camera.dart'; +import 'package:flutter/material.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé + +Future main() async { + // Ensure that plugin services are initialized so that `availableCameras()` + // can be called before `runApp()` + WidgetsFlutterBinding.ensureInitialized(); + + // Obtain a list of the available cameras on the device. + final cameras = await availableCameras(); + + // Get a specific camera from the list of available cameras. + final firstCamera = cameras.first; + Events? events; + + runApp( + MaterialApp( + theme: ThemeData.dark(), + home: CameraEdit( + // Pass the appropriate camera to the TakePictureScreen widget. + camera: firstCamera, + events: events), + ), + ); +} + +// A screen that allows users to take a picture using a given camera. +class CameraEdit extends StatefulWidget { + const CameraEdit({super.key, required this.camera, required this.events}); + final Events? events; + + final CameraDescription camera; + + @override + CameraEditState createState() => CameraEditState(); +} + +class CameraEditState extends State { + late CameraController _controller; + late Future _initializeControllerFuture; + final AuthService _authService = AuthService(); + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + // To display the current output from the Camera, + // create a CameraController. + + _controller = CameraController( + // Get a specific camera from the list of available cameras. + widget.camera, + // Define the resolution to use. + ResolutionPreset.medium, + ); + + // Next, initialize the controller. This returns a Future. + _initializeControllerFuture = _controller.initialize(); + } + + Future pickImage() async { + final imagePicker = ImagePicker(); + final pickedFile = await imagePicker.pickImage(source: ImageSource.gallery); + if (pickedFile != null) { + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => EditEvent( + // Pass the automatically generated path to + // the DisplayPictureScreen widget. + events: widget.events, + imgPath: pickedFile.path, + ), + ), + ); + } + } + + @override + void dispose() { + // Dispose of the controller when the widget is disposed. + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.take_picture ?? + 'Take a picture')), + // You must wait until the controller is initialized before displaying the + // camera preview. Use a FutureBuilder to display a loading spinner until the + // controller has finished initializing. + drawer: MyDrawer(), + body: FutureBuilder( + future: _initializeControllerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // If the Future is complete, display the preview. + return CameraPreview(_controller); + } else { + // Otherwise, display a loading indicator. + return const Center(child: CircularProgressIndicator()); + } + }, + ), + floatingActionButton: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FloatingActionButton( + onPressed: pickImage, + child: Icon(Icons.photo_library), + ), + SizedBox(width: 40), + FloatingActionButton( + // Provide an onPressed callback. + onPressed: () async { + // Take the Picture in a try / catch block. If anything goes wrong, + // catch the error. + try { + // Ensure that the camera is initialized. + await _initializeControllerFuture; + + // Attempt to take a picture and get the file `image` + // where it was saved. + final image = await _controller.takePicture(); + + if (!context.mounted) return; + + // If the picture was taken, display it on a new screen. + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => EditEvent( + // Pass the automatically generated path to + // the DisplayPictureScreen widget. + events: widget.events, + imgPath: image.path, + ), + ), + ); + } catch (e) { + // If an error occurs, log the error to the console. + print(e); + } + }, + child: const Icon(Icons.camera_alt), + ) + ], + ))); + } +} diff --git a/covas_mobile_new/lib/pages/DisplayPictureScreen.dart b/covas_mobile_new/lib/pages/DisplayPictureScreen.dart new file mode 100644 index 0000000..aea3380 --- /dev/null +++ b/covas_mobile_new/lib/pages/DisplayPictureScreen.dart @@ -0,0 +1,268 @@ +import 'package:flutter/material.dart'; +import 'dart:io'; +import '../classes/addEventImage.dart'; +import '../classes/alert.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_gemini/flutter_gemini.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; +import "ItemMenu.dart"; +import 'UpdateEventImage.dart'; + +import 'dart:convert'; + +import '../variable/globals.dart' as globals; +import '../classes/MyDrawer.dart'; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + ); + } +} + +// A screen that allows users to take a picture using a given camera. +class DisplayPictureScreen extends StatefulWidget { + final String imagePath; + + const DisplayPictureScreen({super.key, required this.imagePath}); + + @override + DisplayPictureScreenState createState() => DisplayPictureScreenState(); +} + +// A widget that displays the picture taken by the user. +class DisplayPictureScreenState extends State + with ShowDescImageAdd, ShowAlertDialog, TickerProviderStateMixin { + BannerAd? _bannerAd; + final AuthService _authService = AuthService(); + + late AnimationController controller; + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + + controller = AnimationController( + /// [AnimationController]s can be created with `vsync: this` because of + /// [TickerProviderStateMixin]. + vsync: this, + duration: const Duration(seconds: 5), + )..addListener(() { + setState(() {}); + }); + controller.repeat(reverse: false); + + _getEventInfosFromImage(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + Future displayError(String e) async { + print("problem gemini : ${e}"); + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? 'Error', + AppLocalizations.of(context)?.error_ia ?? + 'Google AI failed to analyze picture. Retry with another one'); + } + + void _showErrorDialog(BuildContext context, String title, String message) { + showAlertDialog(context, title, message); + } + + Future?> _fetchGeolocation(String place) async { + final apiKey = dotenv.env['PLACE_API_KEY'] ?? ''; + final response = await http.get(Uri.parse( + 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${place}}&key=$apiKey')); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + if (data['results'].isNotEmpty) { + return data['results'][0]['geometry']['location']; + } + } + return null; + } + + Future _isDuplicateEvent(String accessToken, + Map jsonData, Map location) async { + final url = Uri.parse( + "${globals.api}/events/search?item=${jsonData["name"]}&date_event=${jsonData["start_date"]}" + "&min_lat=${location['lat']}&max_lat=${location['lat']}" + "&min_lon=${location['lng']}&max_lon=${location['lng']}"); + + final response = await http.get(url, + headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'}); + + if (response.statusCode == 200) { + final events = jsonDecode(utf8.decode(response.bodyBytes)); + return events.isNotEmpty; + } + return false; + } + + Future searchEvents(String json, String imagePath) async { + print(json.replaceAll("'''json", '').replaceAll("'''", "")); + SharedPreferences prefs = await SharedPreferences.getInstance(); + try { + Map jsonData = + jsonDecode(json.replaceAll("```json", '').replaceAll("```", "")); + print("json : ${jsonData}"); + var name = jsonData["name"]; + print("name : ${name}"); + var place = jsonData["place"]; + var date = jsonData["start_date"]; + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + final location = await _fetchGeolocation(place); + if (location == null) { + _showErrorDialog( + context, + AppLocalizations.of(context)?.error ?? 'Error', + AppLocalizations.of(context)?.no_data_geo ?? + 'No geographical data'); + return; + } + + final url = Uri.parse( + "${globals.api}/events/search?item=${name}&date_event=${date}" + "&min_lat=${location['lat']}&max_lat=${location['lat']}" + "&min_lon=${location['lng']}&max_lon=${location['lng']}"); + + final response = await http.get(url, + headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'}); + if (response.statusCode == 200) { + var events = jsonDecode(utf8.decode(response.bodyBytes)); + print("reponse http : ${events.length}"); + if (events.length == 0) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => UpdateeventImage( + events: jsonData, imagePath: imagePath))); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ItemMenu(title: events[0]["id"]))); + } + } else { + String error = AppLocalizations.of(context)?.response_status_update ?? + 'Response status update : ${response.statusCode}'; + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? 'Error', + "${error} : ${response.statusCode}"); + } + } else { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? 'Error', + AppLocalizations.of(context)?.error_token ?? "Token error"); + } + } catch (e) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.error_format ?? + "Data format error given by AI"); + } + + //showDescImageAddDialog(context, message); + } + + Future _getEventInfosFromImage() async { + await dotenv.load(); + + final gemini = Gemini.init( + apiKey: dotenv.env['GEMINI_API_KEY']!, enableDebugging: true); + + final file = File(widget.imagePath); + + gemini + .textAndImage( + text: + "Peux-tu donner le nom, la date (si l'année n'est pas précisé, mettez l'année actuelle ou future) et le lieu de l'évènement sous format JSON (sans le caratère json au début de la chaine de caractère) avec les valeurs suivantes : name, place, description, tags (tableau sans espace), organizers (tableau), start_date et end_date (si le end_date est vide, alors donnez une valeur de six de plus par rapport à start_date) sous le format en YYYY-MM-DD HH:mm:ssZ", + images: [file.readAsBytesSync()], + modelName: "models/gemini-1.5-pro-latest") + .then((value) => searchEvents( + value?.content?.parts?.last.text ?? '', widget.imagePath)) + .catchError((e) => displayError); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.display_picture ?? + "Display The Picture")), + // The image is stored as a file on the device. Use the `Image.file` + // constructor with the given path to display the image. + drawer: MyDrawer(), + body: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!)), + Text( + AppLocalizations.of(context)?.analyze_image ?? + 'Image analyze in progress', + style: Theme.of(context).textTheme.titleLarge, + ), + CircularProgressIndicator( + value: controller.value, + semanticsLabel: + AppLocalizations.of(context)?.loading_progress ?? + 'Loading progress', + ), + ]))); + } +} diff --git a/covas_mobile_new/lib/pages/EditEvent.dart b/covas_mobile_new/lib/pages/EditEvent.dart new file mode 100644 index 0000000..d0519c3 --- /dev/null +++ b/covas_mobile_new/lib/pages/EditEvent.dart @@ -0,0 +1,921 @@ +import 'package:covas_mobile/pages/CameraEdit.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:textfield_tags/textfield_tags.dart'; + +import 'dart:convert'; +import 'dart:io'; + +import '../classes/events.dart'; +import '../classes/MyDrawer.dart'; + +import 'package:camera/camera.dart'; + +import '../classes/alert.dart'; +import '../classes/eventAdded.dart'; + +import '../variable/globals.dart' as globals; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + Events? events; + + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: EditEvent( + events: events, + imgPath: "", + ), + ); + } +} + +class EditEvent extends StatefulWidget { + const EditEvent({Key? key, required this.events, required this.imgPath}) + : super(key: key); + final Events? events; + + final String imgPath; + + @override + _EditEventState createState() => _EditEventState(); +} + +class _EditEventState extends State + with ShowAlertDialog, ShowEventDialog { + BannerAd? _bannerAd; + final AuthService _authService = AuthService(); + + TextEditingController inputName = TextEditingController(); + + TextEditingController inputTicket = TextEditingController(); + TextEditingController inputLink = TextEditingController(); + TextEditingController inputDate = TextEditingController(); + TextEditingController inputDesc = TextEditingController(); + + TextEditingController inputGeo = TextEditingController(); + + TextEditingController startDatepicker = TextEditingController(); + TextEditingController startTimepicker = TextEditingController(); + TextEditingController endDatepicker = TextEditingController(); + TextEditingController endTimepicker = TextEditingController(); + final _stringTagController = StringTagController(); + + DateTime startDate = DateTime.now(); + DateTime endDate = DateTime.now(); + List> suggestions = []; + String geographicalZone = ""; + String imgUrl = ""; + + List initialTags = []; + + final _stringOrgaController = StringTagController(); + List initialOrga = []; + + onTapFunctionDatePicker( + {required BuildContext context, required String position}) async { + DateTime date; + if ((startDatepicker.text.isEmpty) || (endDatepicker.text.isEmpty)) { + date = DateTime.now(); + } else { + date = DateTime.parse(formatDate(startDatepicker.text)); + if (position == "end") { + date = DateTime.parse(formatDate(endDatepicker.text)); + } + } + DateTime? pickedDate = await showDatePicker( + context: context, + firstDate: date, + initialDate: date, + lastDate: DateTime(2104)); + if (pickedDate == null) return; + if (position == "start") { + startDatepicker.text = DateFormat("dd/MM/yyyy").format(pickedDate); + } + if (position == "end") { + endDatepicker.text = DateFormat("dd/MM/yyyy").format(pickedDate); + } + } + + onTapFunctionTimePicker( + {required BuildContext context, required String position}) async { + TimeOfDay time; + + if ((startTimepicker.text.isEmpty) || (endTimepicker.text.isEmpty)) { + time = TimeOfDay.now(); + } else { + DateTime date = new DateTime.now(); + date = date.copyWith( + hour: int.parse(startTimepicker.text.split(":")[0]), + minute: int.parse(startTimepicker.text.split(":")[1])); + time = TimeOfDay.fromDateTime(date); + if (position == "end") { + date = date.copyWith( + hour: int.parse(endTimepicker.text.split(":")[0]), + minute: int.parse(endTimepicker.text.split(":")[1])); + time = TimeOfDay.fromDateTime(date); + } + } + + TimeOfDay? pickedDate = + await showTimePicker(context: context, initialTime: time); + if (pickedDate == null) return; + if (position == "start") { + startTimepicker.text = pickedDate.format(context); + } + if (position == "end") { + endTimepicker.text = pickedDate.format(context); + } + } + + convertNulltoEmptyString(var check) { + if (check == null) { + return ""; + } + return check; + } + + convertNulltoArray(List check) { + if (check == null) { + return []; + } + return check; + } + + String formatDate(String date) { + var splitedDate = date.split("/"); + + var day = splitedDate[0]; + var month = splitedDate[1]; + var year = splitedDate[2]; + + return "${year}-${month}-${day}"; + } + + Future _updateEvent(BuildContext context) async { + if (!_isEventInFuture()) { + _showErrorDialog( + context, + AppLocalizations.of(context)?.error_event ?? "Event error", + AppLocalizations.of(context)?.no_future_event ?? "No future event"); + return; + } + + final accessToken = await _getAccessToken(); + if (accessToken.isEmpty) { + _showErrorDialog( + context, + AppLocalizations.of(context)?.error_user ?? "User error", + AppLocalizations.of(context)?.empty_input ?? "Empty input"); + return; + } + + try { + await dotenv.load(); + final geolocation = await _fetchGeolocation(); + if (geolocation == null) { + _showErrorDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.no_data_geo ?? + "No geographical data"); + return; + } + + if (await _isDuplicateEvent(accessToken, geolocation)) { + _showErrorDialog( + context, + AppLocalizations.of(context)?.info_event ?? "Event info", + AppLocalizations.of(context)?.event_already ?? + "Event already exists"); + return; + } + + if (widget.imgPath.isNotEmpty) { + imgUrl = await _uploadImage(widget.imgPath); + if (imgUrl.isEmpty) { + _showErrorDialog( + context, + AppLocalizations.of(context)?.picture_error ?? "Error picture", + AppLocalizations.of(context)?.no_picture_published ?? + "No picture published"); + return; + } + } + + await _updateEventData(accessToken, geolocation); + String message = + AppLocalizations.of(context)?.event_update ?? "Event updated"; + showEventDialog(context, "${message} : ${inputName.text}"); + } catch (e) { + _showErrorDialog( + context, AppLocalizations.of(context)?.error ?? "Error", "$e"); + } + } + + bool _isEventInFuture() { + DateTime startDateCompare = DateTime.parse( + "${formatDate(startDatepicker.text)}T${startTimepicker.text.replaceAll('-', ':')}"); + return startDateCompare.isAfter(DateTime.now()); + } + + Future _getAccessToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString("access_token") ?? ""; + } + + Future?> _fetchGeolocation() async { + final apiKey = dotenv.env['PLACE_API_KEY'] ?? ''; + final response = await http.get(Uri.parse( + 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${inputGeo.text}&key=$apiKey')); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + if (data['results'].isNotEmpty) { + return data['results'][0]['geometry']['location']; + } + } + return null; + } + + Future _isDuplicateEvent( + String accessToken, Map location) async { + final url = Uri.parse( + "${globals.api}/events/search?item=${inputName.text}&date_event=${formatDate(startDatepicker.text)}" + "&min_lat=${location['lat']}&max_lat=${location['lat']}" + "&min_lon=${location['lng']}&max_lon=${location['lng']}"); + + final response = await http.get(url, + headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'}); + + if (response.statusCode == 200) { + final events = jsonDecode(utf8.decode(response.bodyBytes)); + return events.isNotEmpty && events[0]["id"] != widget.events!.id; + } + return false; + } + + Future _uploadImage(String imagePath) async { + final params = { + 'expiration': '15552000', + 'key': dotenv.env["IMGBB_API_KEY"] + }; + final url = Uri.parse('https://api.imgbb.com/1/upload') + .replace(queryParameters: params); + + final image = File(imagePath); + final req = http.MultipartRequest('POST', url) + ..fields['image'] = base64.encode(await image.readAsBytes()); + + final response = await http.Response.fromStream(await req.send()); + return response.statusCode == 200 + ? json.decode(response.body)["data"]["url"] + : ""; + } + + Future _updateEventData( + String accessToken, Map location) async { + final url = Uri.parse("${globals.api}/events/${widget.events!.id}"); + final response = await http.put(url, + headers: { + HttpHeaders.cookieHeader: 'access_token=$accessToken', + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json' + }, + body: jsonEncode({ + 'name': inputName.text, + 'place': inputGeo.text, + 'start_date': + "${formatDate(startDatepicker.text)}T${startTimepicker.text.replaceAll('-', ':')}", + 'end_date': + "${formatDate(endDatepicker.text)}T${endTimepicker.text.replaceAll('-', ':')}", + 'organizers': + List.from(_stringOrgaController.getTags as List), + 'latitude': location['lat'], + 'longitude': location['lng'], + 'description': inputDesc.text, + "imgUrl": imgUrl, + 'link': inputLink.text, + 'ticket': inputTicket.text, + "tags": List.from(_stringTagController.getTags as List) + })); + + if (response.statusCode != 200 && response.statusCode != 201) { + _handleErrorResponse(context, response.statusCode); + } + } + + void _handleErrorResponse(BuildContext context, int statusCode) { + final messages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + _showErrorDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + messages[statusCode] ?? + AppLocalizations.of(context)?.unknown_error_auth ?? + "Unknown error auth"); + } + + void _showErrorDialog(BuildContext context, String title, String message) { + showAlertDialog(context, title, message); + } + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + inputName.text = widget.events!.name ?? ""; + inputTicket.text = widget.events!.ticket ?? ""; + inputLink.text = widget.events!.link ?? ""; + startDatepicker.text = DateFormat("dd/MM/yyyy") + .format(DateTime.parse( + widget.events!.startDate ?? DateTime.now().toString())) + .toString(); + startTimepicker.text = DateFormat("HH:mm") + .format(DateTime.parse( + widget.events!.startDate ?? DateTime.now().toString())) + .toString(); + endDatepicker.text = DateFormat("dd/MM/yyyy") + .format( + DateTime.parse(widget.events!.endDate ?? DateTime.now().toString())) + .toString(); + endTimepicker.text = DateFormat("HH:mm") + .format( + DateTime.parse(widget.events!.endDate ?? DateTime.now().toString())) + .toString(); + inputGeo.text = widget.events!.place ?? ""; + + imgUrl = widget.events!.imgUrl ?? ""; + inputDesc.text = widget.events!.description ?? ""; + initialTags = List.from(widget.events!.tags as List); + initialOrga = List.from(widget.events!.organizers as List); + } + + final _formKey = GlobalKey(); + String? _validateField(String? value) { + return value!.isEmpty + ? AppLocalizations.of(context)?.required_input ?? "Required input" + : null; + } + + Future searchSuggestions(String input) async { + await dotenv.load(fileName: ".env"); // Load .env file + + final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; + final url = + 'https://api.mapbox.com/geocoding/v5/mapbox.places/${input}.json?access_token=${mapboxAccessToken}&types=poi,address,place'; + var encoded = Uri.encodeFull(url); + final response = await http.get(Uri.parse(encoded)); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + setState(() { + suggestions = (data['features'] as List) + .map((feature) => { + 'place_name': feature['place_name'], + 'text': feature['text'], + 'geometry': feature[ + 'geometry'], // Include geometry for latitude/longitude + }) + .toList(); + }); + } else { + throw Exception('Failed to load suggestions'); + } + } + + Padding _buildGeographicalZoneSearchField() { + return Padding( + padding: + const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), + child: Column( + children: [ + TextField( + controller: inputGeo, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)?.location ?? "Location", + border: OutlineInputBorder(), + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + setState(() { + inputGeo.clear(); // Clear the text field + geographicalZone = ''; // Reset the geographical zone state + suggestions.clear(); // Optionally clear suggestions + }); + }, + ), + ), + onChanged: (value) { + setState(() { + geographicalZone = value; + searchSuggestions(value); + }); + }, + ), + if (suggestions.isNotEmpty) + Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: Colors.blue), + borderRadius: BorderRadius.circular(8), + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: suggestions.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(suggestions[index]['text']), + subtitle: Text(suggestions[index]['place_name']), + onTap: () async { + print("suggestion tapped : ${suggestions[index]}"); + final latitude = + suggestions[index]['geometry']['coordinates'][1]; + final longitude = + suggestions[index]['geometry']['coordinates'][0]; + + setState(() { + geographicalZone = suggestions[index]['text']; + inputGeo.text = geographicalZone; + suggestions.clear(); + }); + }, + ); + }, + ), + ), + ], + ), + ); + } + + Future popCamera() async { + await availableCameras().then((value) => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + CameraEdit(camera: value.first, events: widget.events)))); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + drawer: MyDrawer(), + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.add_event ?? + "Add or Update a event"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!)), + if (widget.imgPath.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 60.0), + child: Center( + child: Container( + width: 200, + height: 150, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100.0)), + child: Image.file(File(widget.imgPath))), + ), + ), + if (widget.imgPath.isEmpty) + Padding( + padding: const EdgeInsets.only(top: 60.0), + child: Image.network( + imgUrl, + width: MediaQuery.of(context).size.width * + 0.5, // 50% of screen width + height: MediaQuery.of(context).size.height * 0.5, + loadingBuilder: (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; // The image has finished loading + } + return Center( + child: CircularProgressIndicator(), + ); + }, + errorBuilder: (BuildContext context, Object error, + StackTrace? stackTrace) { + return Center( + child: Icon(Icons.error, + size: MediaQuery.of(context).size.width * 0.1), + ); + }, + )), + Padding( + padding: EdgeInsets.symmetric(horizontal: 15), + child: ElevatedButton.icon( + onPressed: popCamera, + icon: Icon(Icons.edit, size: 16), // Edit icon + label: Text(AppLocalizations.of(context)?.edit_image ?? + "Edit pictures"), // Button text + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, // Button color + foregroundColor: Colors.white, // Text color + padding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), + ), + ), + ), + Padding( + //padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0), + padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.name ?? "Name", + hintText: + AppLocalizations.of(context)?.edit_event_name ?? + "Edit event name"), + ), + ), + _buildGeographicalZoneSearchField(), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: startDatepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.start_date ?? + "Start date", + hintText: AppLocalizations.of(context)?.select_date ?? + "Click to select a date"), + onTap: () => onTapFunctionDatePicker( + context: context, position: "start")), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: startTimepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.start_time ?? + "Start time", + hintText: AppLocalizations.of(context)?.select_time ?? + "Click to select a time"), + onTap: () => onTapFunctionTimePicker( + context: context, position: "start")), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: endDatepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.end_date ?? + "End date", + hintText: AppLocalizations.of(context)?.select_time ?? + "Click to select a date"), + onTap: () => onTapFunctionDatePicker( + context: context, position: "end")), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: endTimepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.end_time ?? + "End time", + hintText: AppLocalizations.of(context)?.select_time ?? + "Click to select a time"), + onTap: () => onTapFunctionTimePicker( + context: context, position: "end")), + ), + Padding( + //padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0), + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + child: TextFormField( + controller: inputLink, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.link ?? "Link", + hintText: AppLocalizations.of(context)?.edit_link ?? + "Edit link event"), + ), + ), + Padding( + //padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0), + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + child: TextFormField( + controller: inputTicket, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.ticket ?? "Ticket", + hintText: AppLocalizations.of(context)?.edit_ticket ?? + "Edit ticket link"), + ), + ), + TextFieldTags( + textfieldTagsController: _stringTagController, + initialTags: initialTags, + textSeparators: const [' ', ','], + validator: (String tag) { + if (_stringTagController.getTags!.contains(tag)) { + return AppLocalizations.of(context)?.already_tag ?? + "You have already entered this tag"; + } + return null; + }, + inputFieldBuilder: (context, inputFieldValues) { + return Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + child: TextField( + controller: inputFieldValues.textEditingController, + focusNode: inputFieldValues.focusNode, + onChanged: inputFieldValues.onTagChanged, + onSubmitted: inputFieldValues.onTagSubmitted, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.tag ?? 'Tags', + hintText: inputFieldValues.tags.isNotEmpty + ? '' + : AppLocalizations.of(context)?.enter_tag ?? + "Enter tag...", + errorText: inputFieldValues.error, + prefixIcon: inputFieldValues.tags.isNotEmpty + ? SingleChildScrollView( + controller: + inputFieldValues.tagScrollController, + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 4.0, + spacing: 4.0, + children: inputFieldValues.tags + .map((String tag) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + color: Colors.blue, + ), + margin: + const EdgeInsets.symmetric( + horizontal: 5.0), + padding: + const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 5.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + child: Text( + '$tag', + style: const TextStyle( + color: Colors.white), + ), + onTap: () { + //print("$tag selected"); + }, + ), + const SizedBox(width: 4.0), + InkWell( + child: const Icon( + Icons.cancel, + size: 14.0, + color: Color.fromARGB( + 255, 233, 233, 233), + ), + onTap: () { + inputFieldValues + .onTagRemoved(tag); + }, + ) + ], + ), + ); + }).toList()), + ), + ) + : null, + ), + ), + ); + }), + TextFieldTags( + textfieldTagsController: _stringOrgaController, + initialTags: initialOrga, + textSeparators: const [','], + validator: (String tag) { + if (_stringOrgaController.getTags!.contains(tag)) { + return AppLocalizations.of(context) + ?.already_organiser ?? + "You have already entered this organizer"; + } + return null; + }, + inputFieldBuilder: (context, inputFieldValues) { + return Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + child: TextField( + controller: inputFieldValues.textEditingController, + focusNode: inputFieldValues.focusNode, + onChanged: inputFieldValues.onTagChanged, + onSubmitted: inputFieldValues.onTagSubmitted, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.organizer ?? + "Organizer", + hintText: inputFieldValues.tags.isNotEmpty + ? '' + : AppLocalizations.of(context) + ?.enter_organizer ?? + "Enter a organizer", + errorText: inputFieldValues.error, + prefixIcon: inputFieldValues.tags.isNotEmpty + ? SingleChildScrollView( + controller: + inputFieldValues.tagScrollController, + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 4.0, + spacing: 4.0, + children: inputFieldValues.tags + .map((String tag) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + color: Colors.blue, + ), + margin: + const EdgeInsets.symmetric( + horizontal: 5.0), + padding: + const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 5.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + child: Text( + '$tag', + style: const TextStyle( + color: Colors.white), + ), + onTap: () { + //print("$tag selected"); + }, + ), + const SizedBox(width: 4.0), + InkWell( + child: const Icon( + Icons.cancel, + size: 14.0, + color: Color.fromARGB( + 255, 233, 233, 233), + ), + onTap: () { + inputFieldValues + .onTagRemoved(tag); + }, + ) + ], + ), + ); + }).toList()), + ), + ) + : null, + ), + ), + ); + }), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextField( + controller: inputDesc, + keyboardType: TextInputType.multiline, + maxLines: 10, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.description ?? + 'Description', + hintText: + AppLocalizations.of(context)?.describe_event ?? + 'Describe event'), + ), + ), + SizedBox( + height: 30, + ), + Container( + height: 50, + width: 250, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(20)), + child: TextButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + _updateEvent(context); + } + }, + child: Text( + AppLocalizations.of(context)?.add ?? 'Add', + style: TextStyle(color: Colors.white, fontSize: 25), + ), + ), + ) + ], + ), + ), + )); + } +} diff --git a/covas_mobile_new/lib/pages/EditProfile.dart b/covas_mobile_new/lib/pages/EditProfile.dart new file mode 100644 index 0000000..ff62847 --- /dev/null +++ b/covas_mobile_new/lib/pages/EditProfile.dart @@ -0,0 +1,396 @@ +import 'package:covas_mobile/classes/MyDrawer.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; + +import 'dart:convert'; +import 'dart:io'; + +import '../classes/MyDrawer.dart'; +import '../pages/LoginDemo.dart'; + +import '../classes/alert.dart'; +import '../classes/eventAdded.dart'; + +import '../variable/globals.dart' as globals; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: EditProfile(), + ); + } +} + +class EditProfile extends StatefulWidget { + const EditProfile({super.key}); + + @override + _EditProfileState createState() => _EditProfileState(); +} + +class _EditProfileState extends State + with ShowAlertDialog, ShowEventDialog { + BannerAd? _bannerAd; + final AuthService _authService = AuthService(); + + TextEditingController inputUserName = TextEditingController(); + + TextEditingController inputName = TextEditingController(); + + TextEditingController inputFirstName = TextEditingController(); + TextEditingController inputEmail = TextEditingController(); + TextEditingController inputBirth = TextEditingController(); + TextEditingController inputPassword = TextEditingController(); + TextEditingController inputPasswordConfirmed = TextEditingController(); + + onTapFunctionDatePicker({required BuildContext context}) async { + DateTime initialDate = DateTime.parse(formatDate(inputBirth.text)); + + DateTime? pickedDate = await showDatePicker( + context: context, + firstDate: DateTime(1900), + initialDate: initialDate, + lastDate: DateTime(2104)); + if (pickedDate == null) return; + inputBirth.text = DateFormat("dd/MM/yyyy").format(pickedDate); + } + + convertNulltoEmptyString(var check) { + if (check == null) { + return ""; + } + return check; + } + + convertNulltoArray(List check) { + if (check == null) { + return []; + } + return check; + } + + String formatDate(String date) { + var splitedDate = date.split("/"); + + var day = splitedDate[0]; + var month = splitedDate[1]; + var year = splitedDate[2]; + + return "${year}-${month}-${day}"; + } + + Future _updateProfile(BuildContext context) async { + var username = inputUserName.text; + var firstName = inputFirstName.text; + var name = inputName.text; + var email = inputEmail.text; + var password = inputPassword.text; + var confirmedPassword = inputPasswordConfirmed.text; + var birth = DateTime.parse(formatDate(inputBirth.text)); + + if ((password.isNotEmpty) && (confirmedPassword.isNotEmpty)) { + if (password != confirmedPassword) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.different_password_error ?? + "Different password"); + return; + } + } + + var urlPut = Uri.parse("${globals.api}/users/me"); + + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + if (accessToken.isNotEmpty) { + var responsePut = await http.put(urlPut, + headers: { + HttpHeaders.cookieHeader: 'access_token=${accessToken}', + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json' + }, + body: jsonEncode({ + 'name': name, + 'username': username, + 'firstName': firstName, + 'password': password, + 'email': email, + 'roles': '', + 'birth': birth.toString() + })); + print(responsePut.statusCode); + if (responsePut.statusCode == 200) { + showEventDialog(context, + AppLocalizations.of(context)?.user_update ?? "Your user updated"); + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (_) => EditProfile())); + return; + } + + final messages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + final text = messages[responsePut.statusCode] ?? + AppLocalizations.of(context)?.unknown_error_auth ?? + "Unknown error auth"; + showAlertDialog( + context, AppLocalizations.of(context)?.error ?? "Error", text); + } else { + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (_) => LoginDemo())); + } + } + + Future _getInfoProfile() async { + var urlGet = Uri.parse("${globals.api}/users/me"); + + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + if (accessToken.isNotEmpty) { + var responseGet = await http.get(urlGet, headers: { + HttpHeaders.cookieHeader: 'access_token=${accessToken}', + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json' + }); + print(responseGet.statusCode); + if (responseGet.statusCode == 200) { + var body = json.decode(utf8.decode(responseGet.bodyBytes)); + setState(() { + inputName.text = body["name"]; + inputFirstName.text = body["firstName"]; + inputUserName.text = body["username"]; + inputEmail.text = body["email"]; + inputBirth.text = + DateFormat("dd/MM/yyyy").format(DateTime.parse(body["birth"])); + }); + return; + } + + final messages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + final text = messages[responseGet.statusCode] ?? + AppLocalizations.of(context)?.unknown_error_auth ?? + "Unknown error auth"; + showAlertDialog( + context, AppLocalizations.of(context)?.error ?? "Error", text); + } else { + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (_) => LoginDemo())); + } + } + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + _getInfoProfile(); + } + + final _formKey = GlobalKey(); + String? _validateField(String? value) { + return value!.isEmpty + ? AppLocalizations.of(context)?.required_input ?? "Required input" + : null; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: Text( + AppLocalizations.of(context)?.update_profile ?? "Update profile"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + drawer: MyDrawer(), + body: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!)), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputUserName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.name, + hintText: AppLocalizations.of(context)?.edit_pseudo ?? + "Edit pseudo"), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputPassword, + obscureText: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.password ?? + "Password", + hintText: + AppLocalizations.of(context)?.enter_password ?? + "Enter a password"), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputPasswordConfirmed, + obscureText: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.password_confirmed ?? + "Must confirm password", + hintText: + AppLocalizations.of(context)?.password_confirmed ?? + "Must confirm password"), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.last_name ?? + "Last name", + hintText: + AppLocalizations.of(context)?.edit_last_name ?? + "Edit last name"), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputFirstName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.first_name ?? + "First name", + hintText: + AppLocalizations.of(context)?.edit_first_name ?? + "Edit first name"), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputEmail, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.email ?? "Email", + hintText: AppLocalizations.of(context)?.edit_email ?? + "Edit email"), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputBirth, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.birth_date, + hintText: AppLocalizations.of(context)?.edit_birth ?? + "Click to select a birth date"), + onTap: () => onTapFunctionDatePicker(context: context)), + ), + SizedBox( + height: 30, + ), + Container( + height: 50, + width: 250, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(20)), + child: TextButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + _updateProfile(context); + } + }, + child: Text( + AppLocalizations.of(context)?.update_profile ?? + "Update profile ", + style: TextStyle(color: Colors.white, fontSize: 25), + ), + ), + ) + ], + ), + ), + )); + } +} diff --git a/covas_mobile_new/lib/pages/EditSettings.dart b/covas_mobile_new/lib/pages/EditSettings.dart new file mode 100644 index 0000000..c6e36f9 --- /dev/null +++ b/covas_mobile_new/lib/pages/EditSettings.dart @@ -0,0 +1,170 @@ +import 'package:covas_mobile/classes/MyDrawer.dart'; +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'dart:convert'; +import 'dart:io'; + +import '../classes/MyDrawer.dart'; + +import '../classes/alert.dart'; +import '../classes/eventAdded.dart'; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import '../classes/auth_service.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: EditSettings(), + ); + } +} + +class EditSettings extends StatefulWidget { + const EditSettings({super.key}); + + @override + _EditProfileState createState() => _EditProfileState(); +} + +class _EditProfileState extends State + with ShowAlertDialog, ShowEventDialog { + BannerAd? _bannerAd; + final AuthService _authService = AuthService(); + + TextEditingController inputUserName = TextEditingController(); + int? kilometer; + + Future getParameter() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + setState(() { + var kilometer = prefs.getDouble("kilometer")?.toInt() ?? null; + }); + } + + Future setParameter() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + if (kilometer != null) { + prefs.setDouble("kilometer", kilometer?.toDouble() ?? 50); + showAlertDialog( + context, + AppLocalizations.of(context)?.updated ?? "Updated", + AppLocalizations.of(context)?.settings_updated ?? "Settings updated"); + } + } + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + getParameter(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.settings ?? "Settings"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + drawer: MyDrawer(), + body: Form( + child: SingleChildScrollView( + child: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!)), + Padding( + padding: const EdgeInsets.only( + left: 15.0, + right: 15.0, + top: 15.0, + bottom: 0.0, + ), + child: DropdownButtonFormField( + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.define_kilometer ?? + 'Define kilometer', + ), + value: + kilometer, // Set the initial selected value here, or leave as `null` if unselected. + items: [ + DropdownMenuItem( + value: 5, + child: Text('5km'), + ), + DropdownMenuItem( + value: 25, + child: Text('25km'), + ), + DropdownMenuItem( + value: 50, + child: Text('50km'), + ), + DropdownMenuItem( + value: 75, + child: Text('75km'), + ), + DropdownMenuItem( + value: 100, + child: Text('100km'), + ), + ], + onChanged: (int? newValue) { + // Handle selection + kilometer = newValue; + }, + ), + ), + SizedBox( + height: 30, + ), + Container( + height: 50, + width: 250, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(20)), + child: TextButton( + onPressed: () {}, + child: Text( + AppLocalizations.of(context)?.update ?? "Update", + style: TextStyle(color: Colors.white, fontSize: 25), + ), + ), + ) + ], + ), + ), + )); + } +} diff --git a/covas_mobile_new/lib/pages/ForgotPassword.dart b/covas_mobile_new/lib/pages/ForgotPassword.dart new file mode 100644 index 0000000..fb216c1 --- /dev/null +++ b/covas_mobile_new/lib/pages/ForgotPassword.dart @@ -0,0 +1,176 @@ +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:intl/intl.dart'; + +import 'dart:convert'; +import 'dart:io'; + +import '../main.dart'; + +import '../classes/alert.dart'; + +import '../variable/globals.dart' as globals; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: PasswordForgot(), + ); + } +} + +class PasswordForgot extends StatefulWidget { + const PasswordForgot({super.key}); + + @override + _PasswordForgotState createState() => _PasswordForgotState(); +} + +class _PasswordForgotState extends State with ShowAlertDialog { + TextEditingController inputEmail = TextEditingController(); + + convertNulltoEmptyString(var check) { + if (check == null) { + return ""; + } + return check; + } + + convertNulltoArray(List check) { + if (check == null) { + return []; + } + return check; + } + + String formatDate(String date) { + var splitedDate = date.split("/"); + + var day = splitedDate[0]; + var month = splitedDate[1]; + var year = splitedDate[2]; + + return "${year}-${month}-${day}"; + } + + Future _forgotPassword(BuildContext context) async { + var email = inputEmail.text; + + var urlPut = Uri.parse("${globals.api}/password/forgot"); + + var responsePost = await http.post(urlPut, + headers: { + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json' + }, + body: jsonEncode({ + 'email': email, + })); + print(responsePost.statusCode); + if (responsePost.statusCode == 200) { + String message = + AppLocalizations.of(context)?.email_sent ?? "Email has been sent"; + showAlertDialog( + context, + AppLocalizations.of(context)?.create ?? "Creation", + "${message} : ${email}"); + return; + } + + final messages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + + final text = messages[responsePost.statusCode] ?? + AppLocalizations.of(context)?.unknown_error_auth ?? + "Unknown error auth"; + showAlertDialog( + context, AppLocalizations.of(context)?.error ?? "Error", text); + } + + @override + void initState() { + super.initState(); + } + + final _formKey = GlobalKey(); + String? _validateField(String? value) { + return value!.isEmpty ? 'Champ requis' : null; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.forgot_password ?? + "Forgot password"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputEmail, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.email ?? 'Email', + hintText: AppLocalizations.of(context)?.enter_email ?? + 'Enter the email'), + ), + ), + SizedBox( + height: 30, + ), + Container( + height: 50, + width: 250, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(20)), + child: TextButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + _forgotPassword(context); + } + }, + child: Text( + AppLocalizations.of(context)?.send_email ?? 'Send email', + style: TextStyle(color: Colors.white, fontSize: 25), + ), + ), + ) + ], + ), + ), + )); + } +} diff --git a/covas_mobile_new/lib/pages/ItemMenu.dart b/covas_mobile_new/lib/pages/ItemMenu.dart new file mode 100644 index 0000000..def7c59 --- /dev/null +++ b/covas_mobile_new/lib/pages/ItemMenu.dart @@ -0,0 +1,455 @@ +// ignore_for_file: unnecessary_brace_in_string_interps + +import 'dart:io'; +import 'dart:convert'; + +import 'package:covas_mobile/classes/alert.dart'; +import 'package:covas_mobile/pages/ListItemByTags.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import '../variable/globals.dart' as globals; + +import '../classes/events.dart'; +import '../classes/MyDrawer.dart'; + +import 'ListItemMenu.dart'; +import 'MapboxPages.dart'; +import 'ListItemByOrganizers.dart'; +import 'EditEvent.dart'; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import '../classes/auth_service.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + initializeDateFormatting("fr_FR", null).then((_) => (const MyApp())); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + // This widget is the root of your application. + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + ), + home: const ItemMenu(title: 'Flutter Demo Home Page'), + ); + } +} + +class ItemMenu extends StatefulWidget { + const ItemMenu({Key? key, required this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + @override + State createState() => _ItemMenuState(); +} + +class _ItemMenuState extends State with ShowAlertDialog { + BannerAd? _bannerAd; + final AuthService _authService = AuthService(); + + String listUser = ""; + String eventName = ""; + String eventStartDate = ""; + String eventDescription = ""; + String eventTicket = ""; + String eventLink = ""; + String place = ""; + String imgUrl = ""; + List tags = []; + List organizers = []; + + String id = ""; + + Events? events; + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + + _getEventInfos(); + } + + Future _getEventInfos() async { + final prefs = await SharedPreferences.getInstance(); + final accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isEmpty) { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.invalid_cache ?? "Invalid cache"); + return; + } + + final urlGet = Uri.parse("${globals.api}/events/${widget.title}"); + final responseGet = await http.get( + urlGet, + headers: {HttpHeaders.cookieHeader: 'access_token=$accessToken'}, + ); + + if (responseGet.statusCode == 200) { + final responseBody = utf8.decode(responseGet.bodyBytes); + + events = Events.fromJson(jsonDecode(responseBody)); + final locale = Provider.of(context, listen: false) + .locale + ?.toString() ?? + 'en_US'; + final startDate = + DateTime.parse(events?.startDate ?? DateTime.now().toString()); + //final date = DateFormat.yMd().format(startDate); + //final time = DateFormat.Hm().format(startDate); + final endDate = + DateTime.parse(events?.endDate ?? DateTime.now().toString()); + String separator = AppLocalizations.of(context)?.at ?? "at"; + final formattedStartDate = + DateFormat("EEEE d MMMM y '${separator}' HH:mm", locale) + .format(startDate); + + final formattedEndDate = + DateFormat("EEEE d MMMM y '${separator}' HH:mm", locale) + .format(endDate); + + String link = AppLocalizations.of(context)?.to_date ?? "to"; + + setState(() { + eventName = events?.name ?? ""; + + eventStartDate = "$formattedStartDate ${link} $formattedEndDate"; + organizers = List.from(events?.organizers ?? []); + place = events?.place ?? ""; + imgUrl = events?.imgUrl ?? ""; + eventLink = events?.link ?? ""; + eventTicket = events?.ticket ?? ""; + eventDescription = events?.description ?? ""; + tags = List.from(events?.tags ?? []); + }); + } else { + final messages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + + final errorMessage = messages[responseGet.statusCode] ?? + AppLocalizations.of(context)?.unknown_error_auth ?? + "Unknown error auth"; + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + errorMessage); + } + } + + final _formKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text("${eventName}", overflow: TextOverflow.ellipsis), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + leading: IconButton( + icon: Icon(Icons.arrow_back), + onPressed: () { + Navigator.push( + context, MaterialPageRoute(builder: (_) => ListItemMenu())); + }, + )), + drawer: MyDrawer(), + body: SingleChildScrollView( + child: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!)), + Padding( + padding: const EdgeInsets.only(top: 60.0), + child: Image.network( + imgUrl, + width: MediaQuery.of(context).size.width * + 0.5, // 50% of screen width + height: MediaQuery.of(context).size.height * 0.5, + loadingBuilder: (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; // The image has finished loading + } + return Center( + child: CircularProgressIndicator(), + ); + }, + errorBuilder: (BuildContext context, Object error, + StackTrace? stackTrace) { + return Center( + child: Icon(Icons.error, + size: MediaQuery.of(context).size.width * 0.1), + ); + }, + )), + Row(children: [ + Icon(Icons.event), + Text( + AppLocalizations.of(context)?.item_date ?? "Date : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold), + ) + ]), + Row( + children: [ + Flexible( + child: Text("${eventStartDate}", + style: TextStyle(fontSize: 15.0))) + ], + ), + Row(children: [ + Icon(Icons.explore), + Text( + AppLocalizations.of(context)?.item_maps ?? "Maps : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold), + ) + ]), + Row(children: [ + Flexible( + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => MapboxPages( + title: '${widget.title}', + place: '${place}'))); + }, + child: Text("${place}", + style: TextStyle(fontSize: 15.0), + maxLines: 3, + overflow: TextOverflow.ellipsis))) + ]), + Row(children: [ + Icon(Icons.link), + Text( + AppLocalizations.of(context)?.item_link ?? "Link : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold), + ) + ]), + Row( + children: [ + Flexible( + child: + Text("${eventLink}", style: TextStyle(fontSize: 15.0))) + ], + ), + Row(children: [ + Icon(Icons.add_shopping_cart), + Text( + AppLocalizations.of(context)?.item_ticket ?? "Ticket : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold), + ) + ]), + Row( + children: [ + Flexible( + child: Text("${eventTicket}", + style: TextStyle(fontSize: 15.0))) + ], + ), + Row(children: [ + Icon(Icons.group), + Text( + AppLocalizations.of(context)?.item_organizer ?? "Organizers : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold), + ) + ]), + Row(children: [ + Flexible( + flex: 3, + fit: FlexFit.tight, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 2.0, + spacing: 2.0, + children: organizers.map((String tag) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + color: Colors.blue, + ), + margin: const EdgeInsets.symmetric(horizontal: 5.0), + padding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ListItemOrganizers( + organizer: '$tag'))); + }, + child: Text( + '$tag', + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + ); + }).toList()), + )), + ]), + Row(children: [ + Icon(Icons.description), + Text( + AppLocalizations.of(context)?.item_description ?? + "Description : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold)) + ]), + Row(children: [ + Flexible( + child: Text("${eventDescription}", + style: TextStyle(fontSize: 15.0), + maxLines: 3, + overflow: TextOverflow.ellipsis)) + ]), + Row(children: [ + Icon(Icons.category), + Text(AppLocalizations.of(context)?.item_tags ?? "Tags : ", + style: TextStyle(fontSize: 15.0, fontWeight: FontWeight.bold)) + ]), + Row( + children: [ + Flexible( + flex: 3, + fit: FlexFit.tight, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 2.0, + spacing: 2.0, + children: tags.map((String tag) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + color: Colors.blue, + ), + margin: + const EdgeInsets.symmetric(horizontal: 5.0), + padding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => + ListItemTags(tags: '$tag'))); + }, + child: Text( + '$tag', + style: + const TextStyle(color: Colors.white), + ), + ), + ], + ), + ); + }).toList()), + )), + ], + ) + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => EditEvent( + events: events, + imgPath: "", + )), + ); + }, + backgroundColor: Colors.blue, + tooltip: AppLocalizations.of(context)?.search ?? 'Search', + child: const Icon(Icons.edit, color: Colors.white), + ), + ); + } +} diff --git a/covas_mobile_new/lib/pages/ListItemByOrganizers.dart b/covas_mobile_new/lib/pages/ListItemByOrganizers.dart new file mode 100644 index 0000000..65bc4f7 --- /dev/null +++ b/covas_mobile_new/lib/pages/ListItemByOrganizers.dart @@ -0,0 +1,275 @@ +import 'dart:convert'; +import 'dart:io'; +import "ItemMenu.dart"; + +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import '../classes/events.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import '../variable/globals.dart' as globals; +import '../classes/MyDrawer.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // +import '../classes/notification_service.dart'; + +// app starting point +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await initializeDateFormatting("fr_FR", null); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: [ + const Locale('fr', 'FR'), + ], + home: const ListItemOrganizers(organizer: "default"), + debugShowCheckedModeBanner: false, + ); + } +} + +// homepage class +class ListItemOrganizers extends StatefulWidget { + const ListItemOrganizers({Key? key, required this.organizer}) + : super(key: key); + + final String organizer; + @override + State createState() => _MyHomePageState(); +} + +// homepage state +class _MyHomePageState extends State { + int _fetchCount = 0; + bool _isLoading = false; + late ScrollController _scrollController; + + final AuthService _authService = AuthService(); + + void _incrementFetchCount() { + setState(() { + _fetchCount++; + }); + } + + void _scrollListener() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _incrementFetchCount(); + _fetchData(); + } + } + + Future _fetchData() async { + if (_isLoading) return; + setState(() { + _isLoading = true; + }); + + await Future.delayed(Duration(seconds: 2)); + getPosts(widget.organizer, count: _fetchCount); + + setState(() { + _isLoading = false; + }); + } + + Future> toggleInterested(String eventId) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + final url = Uri.parse("${globals.api}/events/${eventId}/interest"); + if (accessToken.isNotEmpty) { + final response = await http.post( + url, + headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=$accessToken" + }, + ); + + if (response.statusCode != 200) { + throw (AppLocalizations.of(context)?.toogle_interest ?? + "Error toogle interest: ${response.statusCode}"); + } + + var event = json.decode(response.body); + return event; + } + return {"interested": false, "interested_count": 0}; + } + + // variable to call and store future list of posts + + // function to fetch data from api and return future list of posts + static Future> getPosts(organizer, {count = 0}) async { + await initializeDateFormatting("fr_FR", null); + + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + final List body = []; + if (accessToken.isNotEmpty) { + DateTime currentDatetime = DateTime.now(); + num limit = 20 * (count + 1); + var url = Uri.parse( + "${globals.api}/events?organizers=${organizer}&limit=${limit}¤t_datetime=${currentDatetime.toString()}"); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=${accessToken}" + }); + final List body = json.decode(utf8.decode(response.bodyBytes)); + return body.map((e) => Events.fromJson(e)).toList(); + } + return body; + } + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + _scrollController = ScrollController(); + _scrollController.addListener(_scrollListener); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + // build function + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: MyDrawer(), + body: Center( + // FutureBuilder + child: FutureBuilder>( + future: getPosts(widget.organizer), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + // until data is fetched, show loader + return const CircularProgressIndicator(); + } else if (snapshot.hasData) { + // once data is fetched, display it on screen (call buildPosts()) + final posts = snapshot.data!; + return buildPosts(posts); + } else { + // if no data, show simple Text + return Text( + AppLocalizations.of(context)?.no_data ?? "No data available"); + } + }, + ), + ), + ); + } + + // function to display fetched data on screen + Widget buildPosts(List posts) { + String organizer = + AppLocalizations.of(context)?.item_organizer ?? "Organizer : "; + // ListView Builder to show data in a list + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text("${organizer}${widget.organizer}", + overflow: TextOverflow.ellipsis), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: ListView.separated( + controller: _scrollController, + itemCount: posts.isNotEmpty + ? posts.length + (_isLoading ? 1 : 0) // Add 1 only if loading + : 0, + itemBuilder: (context, index) { + final post = posts[index]; + final startDate = DateTime.parse(post.startDate!); + + final locale = Provider.of(context, listen: false) + .locale + ?.toString() ?? + 'en_US'; + + final dateLongue = + DateFormat('EEEE d MMMM y', locale).format(startDate); + final countInterestedString = + AppLocalizations.of(context)?.count_interested ?? + "Interested people number"; + final countInterested = + "${countInterestedString} : ${post.interestedCount}"; + + return ListTile( + title: Text('${post.name!}'), + subtitle: + Text('${post.place!}\n${dateLongue}\n${countInterested}'), + trailing: IconButton( + onPressed: () async { + try { + final result = await toggleInterested(post.id!); + setState(() { + post.interested = result["interested"]; + post.interestedCount = result["interested_count"]; + }); + if (result["interested"] == true) { + NotificationService.scheduleEventNotification( + eventId: post.id!, + title: "Rappel évènement", + body: + "Ton évènement '${post.name}' commence dans 1 heure !", + eventDate: DateTime.parse(post.startDate!), + ); + } else { + NotificationService.cancel(post.id!); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)?.error_update ?? + "Error when updating")), + ); + } + }, + icon: Icon( + post.interested ?? false + ? Icons.favorite + : Icons.favorite_border, + color: + post.interested ?? false ? Colors.red : Colors.grey)), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ItemMenu(title: post.id!))); + }); + }, + separatorBuilder: (context, index) { + return Divider(); + }, + ), + ); + } +} diff --git a/covas_mobile_new/lib/pages/ListItemByTags.dart b/covas_mobile_new/lib/pages/ListItemByTags.dart new file mode 100644 index 0000000..1dbd8df --- /dev/null +++ b/covas_mobile_new/lib/pages/ListItemByTags.dart @@ -0,0 +1,277 @@ +import 'dart:convert'; +import 'dart:io'; +import "ItemMenu.dart"; + +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import '../classes/events.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +import '../variable/globals.dart' as globals; + +import '../classes/MyDrawer.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // +import '../classes/notification_service.dart'; + +// app starting point +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await initializeDateFormatting("fr_FR", null); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: [ + const Locale('fr', 'FR'), + ], + home: const ListItemTags(tags: "default"), + debugShowCheckedModeBanner: false, + ); + } +} + +// homepage class +class ListItemTags extends StatefulWidget { + const ListItemTags({Key? key, required this.tags}) : super(key: key); + + final String tags; + @override + State createState() => _MyHomePageState(); +} + +// homepage state +class _MyHomePageState extends State { + // variable to call and store future list of posts + + int _fetchCount = 0; + bool _isLoading = false; + late ScrollController _scrollController; + + final AuthService _authService = AuthService(); + + void _incrementFetchCount() { + setState(() { + _fetchCount++; + }); + } + + void _scrollListener() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _incrementFetchCount(); + + _fetchData(); + } + } + + Future _fetchData() async { + print("Counter : ${_fetchCount}"); + if (_isLoading) return; + setState(() { + _isLoading = true; + }); + + await Future.delayed(Duration(seconds: 2)); + getPosts(widget.tags, count: _fetchCount); + + setState(() { + _isLoading = false; + }); + } + + // function to fetch data from api and return future list of posts + static Future> getPosts(tags, {count = 0}) async { + await initializeDateFormatting("fr_FR", null); + + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + final List body = []; + if (accessToken.isNotEmpty) { + DateTime currentDatetime = DateTime.now(); + num limit = 20 * (count + 1); + + var url = Uri.parse( + "${globals.api}/events?tags=${tags}&limit=${limit}¤t_datetime=${currentDatetime.toString()}"); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=${accessToken}" + }); + final List body = json.decode(utf8.decode(response.bodyBytes)); + return body.map((e) => Events.fromJson(e)).toList(); + } + return body; + } + + Future> toggleInterested(String eventId) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + final url = Uri.parse("${globals.api}/events/${eventId}/interest"); + if (accessToken.isNotEmpty) { + final response = await http.post( + url, + headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=$accessToken" + }, + ); + + if (response.statusCode != 200) { + throw (AppLocalizations.of(context)?.toogle_interest ?? + "Error toogle interest: ${response.statusCode}"); + } + + var event = json.decode(response.body); + return event; + } + return {"interested": false, "interested_count": 0}; + } + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + _authService.checkTokenStatus(context); + _scrollController = ScrollController(); + _scrollController.addListener(_scrollListener); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + // build function + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: MyDrawer(), + body: Center( + // FutureBuilder + child: FutureBuilder>( + future: getPosts(widget.tags), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + // until data is fetched, show loader + return const CircularProgressIndicator(); + } else if (snapshot.hasData) { + // once data is fetched, display it on screen (call buildPosts()) + final posts = snapshot.data!; + return buildPosts(posts); + } else { + // if no data, show simple Text + return Text( + AppLocalizations.of(context)?.no_data ?? "No data available"); + } + }, + ), + ), + ); + } + + // function to display fetched data on screen + Widget buildPosts(List posts) { + // ListView Builder to show data in a list + String tag = AppLocalizations.of(context)?.item_tags ?? "Tags : "; + + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text("${tag}${widget.tags}", overflow: TextOverflow.ellipsis), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: ListView.separated( + controller: _scrollController, + itemCount: posts.isNotEmpty + ? posts.length + (_isLoading ? 1 : 0) // Add 1 only if loading + : 0, + itemBuilder: (context, index) { + final post = posts[index]; + final startDate = DateTime.parse(post.startDate!); + final locale = Provider.of(context, listen: false) + .locale + ?.toString() ?? + 'en_US'; + final dateLongue = + DateFormat('EEEE d MMMM y', locale).format(startDate); + + final countInterestedString = + AppLocalizations.of(context)?.count_interested ?? + "Interested people number"; + final countInterested = + "${countInterestedString} : ${post.interestedCount}"; + + return ListTile( + title: Text('${post.name!}'), + subtitle: + Text('${post.place!}\n${dateLongue}\n${countInterested}'), + trailing: IconButton( + onPressed: () async { + try { + final result = await toggleInterested(post.id!); + setState(() { + post.interested = result["interested"]; + post.interestedCount = result["interested_count"]; + }); + if (result["interested"] == true) { + NotificationService.scheduleEventNotification( + eventId: post.id!, + title: "Rappel évènement", + body: + "Ton évènement '${post.name}' commence dans 1 heure !", + eventDate: DateTime.parse(post.startDate!), + ); + } else { + NotificationService.cancel(post.id!); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)?.error_update ?? + "Error when updating")), + ); + } + }, + icon: Icon( + post.interested ?? false + ? Icons.favorite + : Icons.favorite_border, + color: + post.interested ?? false ? Colors.red : Colors.grey)), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ItemMenu(title: post.id!))); + }); + }, + separatorBuilder: (context, index) { + return Divider(); + }, + ), + ); + } +} diff --git a/covas_mobile_new/lib/pages/ListItemMenu.dart b/covas_mobile_new/lib/pages/ListItemMenu.dart new file mode 100644 index 0000000..7ce2daf --- /dev/null +++ b/covas_mobile_new/lib/pages/ListItemMenu.dart @@ -0,0 +1,954 @@ +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:flutter_dotenv/flutter_dotenv.dart'; // Import dotenv +import 'dart:convert'; +import 'dart:io'; +import 'ItemMenu.dart'; +import '../classes/events.dart'; +import '../classes/MyDrawer.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:intl/intl.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'dart:math'; +import 'package:geolocator/geolocator.dart'; +import '../variable/globals.dart' as globals; +import 'package:permission_handler/permission_handler.dart'; +import "Camera.dart"; +import 'package:camera/camera.dart'; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import '../classes/auth_service.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // Créé plus loin +import '../classes/notification_service.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await MobileAds.instance.initialize(); + await initializeDateFormatting("fr_FR", null); + + runApp(ChangeNotifierProvider( + create: (_) => LocaleProvider(), + child: const MyApp(), + )); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + final localeProvider = Provider.of(context); + return MaterialApp( + localizationsDelegates: [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: [const Locale('fr', 'FR'), const Locale('en')], + locale: localeProvider.locale, + home: Builder(builder: (context) => ListItemMenu()), + debugShowCheckedModeBanner: false, + ); + } +} + +class ListItemMenu extends StatefulWidget { + const ListItemMenu({super.key}); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + BannerAd? _bannerAd; + final AuthService _authService = AuthService(); + late ScrollController _scrollController; + int _fetchCount = 0; + bool _isLoading = false; + + Future> postsFuture = getPosts(); + List filteredPosts = []; + String geographicalZone = ''; + String itemName = ''; + String itemTags = ''; + String query = ''; + List> suggestionsGeo = []; + List> suggestionsItem = []; + List> suggestionsTags = []; + TextEditingController inputGeo = TextEditingController(); + TextEditingController startDatepicker = TextEditingController(); + TextEditingController endDatepicker = TextEditingController(); + TextEditingController inputItem = TextEditingController(); + TextEditingController inputTags = TextEditingController(); + + bool showDateFields = false; // State to toggle date fields + bool showArrow = true; + bool showInputSearch = true; + bool showInputGeo = true; + bool showInputTag = true; + // Fetching events from API + static Future> getPosts() async { + await initializeDateFormatting("fr_FR"); + PermissionStatus status = await Permission.location.status; + final List body = []; + var url = Uri.parse("${globals.api}/events"); + if (status.isGranted) { + print("Location permission granted"); + + // Get the current position with high accuracy + + const LocationSettings locationSettings = LocationSettings( + accuracy: LocationAccuracy.medium, + timeLimit: Duration(seconds: 5), + ); + Position? position; + try { + position = await Geolocator.getCurrentPosition( + locationSettings: locationSettings); + } on LocationServiceDisabledException { + // Handle location services disabled + print('Location services are disabled.'); + position = await Geolocator.getLastKnownPosition(); + if (position == null) { + print('No last known position available.'); + } + } catch (e) { + // Handle other errors + print('Failed to get location: $e'); + position = await Geolocator.getLastKnownPosition(); + if (position == null) { + print('No last known position available.'); + } + } + SharedPreferences prefs = await SharedPreferences.getInstance(); + + if (position != null) { + // Calculate the boundaries + double radiusInKm = prefs.getDouble("kilometer") ?? 50.0; + double latDistance = radiusInKm / 111.0; + double lonDistance = + radiusInKm / (111.0 * cos(position.latitude * pi / 180)); + + double minLat = position.latitude - latDistance; + double maxLat = position.latitude + latDistance; + double minLon = position.longitude - lonDistance; + double maxLon = position.longitude + lonDistance; + DateTime currentDatetime = DateTime.now(); + url = Uri.parse("${globals.api}/events/search" + "?min_lat=$minLat&max_lat=$maxLat" + "&min_lon=$minLon&max_lon=$maxLon¤t_datetime=${currentDatetime.toString()}"); + } + + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=${accessToken}" + }); + final List body = json.decode(utf8.decode(response.bodyBytes)); + return body.map((e) => Events.fromJson(e)).toList(); + } + } + return body; + } + + String formatDate(String date) { + var splitedDate = date.split("-"); + var day = splitedDate[0]; + var month = splitedDate[1]; + var year = splitedDate[2]; + + return "${year}-${month}-${day}"; + } + + void _incrementFetchCount() { + setState(() { + _fetchCount++; + }); + _fetchData(); + } + + void _scrollListener() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _incrementFetchCount(); + } + + // Scroll to top + } + + Future _fetchData() async { + print("Counter : ${_fetchCount}"); + if (_isLoading) return; + setState(() { + _isLoading = true; + }); + + await Future.delayed(Duration(seconds: 2)); + fetchPostsByLocation(); + + setState(() { + _isLoading = false; + }); + } + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + _scrollController = ScrollController(); + _scrollController.addListener(_scrollListener); + + // Initialize data fetch when the page loads + _getCurrentLocation(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + // Get the device's current location + Future _getCurrentLocation() async { + PermissionStatus status = await Permission.location.status; + + if (status.isGranted) { + print("Location permission granted"); + + // Get the current position with high accuracy + const LocationSettings locationSettings = LocationSettings( + accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 5)); + Position? position; + try { + position = await Geolocator.getCurrentPosition( + locationSettings: locationSettings); + } on LocationServiceDisabledException { + // Handle location services disabled + print('Location services are disabled.'); + position = await Geolocator.getLastKnownPosition(); + if (position == null) { + print('No last known position available.'); + } + } catch (e) { + // Handle other errors + print('Failed to get location: $e'); + position = await Geolocator.getLastKnownPosition(); + if (position == null) { + print('No last known position available.'); + } + } + + // Reverse geocode: Get city and country from latitude and longitude using Mapbox Search API + if (position != null) { + _getCityAndCountry(position!.latitude, position!.longitude); + } + } + } + + // Method to get city and country from latitude and longitude using Mapbox API + Future _getCityAndCountry(double latitude, double longitude) async { + await dotenv.load(fileName: ".env"); // Load .env file + + final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; + final url = Uri.parse( + 'https://api.mapbox.com/geocoding/v5/mapbox.places/$longitude,$latitude.json?access_token=$mapboxAccessToken', + ); + + try { + // Send GET request to Mapbox API + final response = await http.get(url); + + // If the request is successful (HTTP status 200) + print("status mapbox : ${response.statusCode}"); + if (response.statusCode == 200) { + // Parse the response body + final data = json.decode(response.body); + + // Extract the city and country from the response + final features = data['features']; + + if (features.isNotEmpty) { + String city = _getCityFromFeatures(features); + String country = _getCountryFromFeatures(features); + print("city : ${city} ${country}"); + if (city.isNotEmpty && country.isNotEmpty) { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setDouble("city_lat", latitude); + prefs.setDouble("city_long", longitude); + fetchPostsByLocation(); + setState(() { + inputGeo.text = "${city}, ${country}"; + }); + } else { + fetchPostsByLocation(); + } + } else { + fetchPostsByLocation(); + } + } else { + fetchPostsByLocation(); + throw Exception('Failed to load location data'); + } + } catch (e) { + fetchPostsByLocation(); + } + } + + // Helper function to extract the city from the Mapbox features array + String _getCityFromFeatures(List features) { + for (var feature in features) { + if (feature['place_type'] != null && + feature['place_type'].contains('place')) { + return feature['text'] ?? ''; + } + } + return ''; + } + + // Helper function to extract the country from the Mapbox features array + String _getCountryFromFeatures(List features) { + for (var feature in features) { + if (feature['place_type'] != null && + feature['place_type'].contains('country')) { + return feature['text'] ?? ''; + } + } + return ''; + } + + Future searchSuggestionsGeo(String input) async { + await dotenv.load(fileName: ".env"); // Load .env file + + final mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; + final url = + 'https://api.mapbox.com/geocoding/v5/mapbox.places/${input}.json?access_token=${mapboxAccessToken}&proximity=ip'; + final response = await http.get(Uri.parse(url)); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + setState(() { + suggestionsGeo = (data['features'] as List) + .map((feature) => { + 'place_name': feature['place_name'], + 'geometry': feature[ + 'geometry'], // Include geometry for latitude/longitude + }) + .toList(); + if (suggestionsGeo.isNotEmpty) { + showArrow = false; + showInputSearch = false; + showInputTag = false; + } + }); + } else { + throw Exception(AppLocalizations.of(context)?.failed_suggestions ?? + 'Failed to load suggestions'); + } + } + + Future getUrlForEvents() async { + final prefs = await SharedPreferences.getInstance(); + final latitude = prefs.getDouble("city_lat") ?? 0.0; + final longitude = prefs.getDouble("city_long") ?? 0.0; + + final radiusInKm = prefs.getDouble("kilometer") ?? 50.0; + String endpoint = "events"; + String queryParameters = ""; + + if (latitude != 0.0 && longitude != 0.0) { + final latDistance = radiusInKm / 111.0; + final lonDistance = radiusInKm / (111.0 * cos(latitude * pi / 180)); + + final minLat = latitude - latDistance; + final maxLat = latitude + latDistance; + final minLon = longitude - lonDistance; + final maxLon = longitude + lonDistance; + + endpoint = "events/search"; + queryParameters = + "min_lat=$minLat&max_lat=$maxLat&min_lon=$minLon&max_lon=$maxLon"; + } + + final currentDate = DateTime.now(); + String dateParameter = "current_datetime=${currentDate.toIso8601String()}"; + + if (startDatepicker.text.isNotEmpty || endDatepicker.text.isNotEmpty) { + endpoint = "events/search"; + if (startDatepicker.text.isNotEmpty) { + final startDate = DateTime.parse(formatDate(startDatepicker.text)); + dateParameter = "start_date=${startDate.toIso8601String()}"; + } + if (endDatepicker.text.isNotEmpty) { + final endDate = DateTime.parse(formatDate(endDatepicker.text)); + dateParameter += "&end_date=${endDate.toIso8601String()}"; + } + } + + if (inputItem.text.isNotEmpty) { + queryParameters += "&item=${inputItem.text}"; + } + + if (inputTags.text.isNotEmpty) { + queryParameters += "&tags=${inputTags.text}"; + } + + if (queryParameters.isNotEmpty) { + queryParameters = "$queryParameters&$dateParameter"; + } else { + queryParameters = dateParameter; + } + int limit = 20 * (_fetchCount + 1); + + return Uri.parse( + "${globals.api}/$endpoint?$queryParameters&limit=${limit}"); + } + + Future searchSuggestionsByItem(String input) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + var url = await getUrlForEvents(); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=$accessToken" + }); + + if (response.statusCode == 200) { + final data = json.decode(utf8.decode(response.bodyBytes)); + setState(() { + suggestionsItem = (data as List) + .map((feature) => {'name': feature['name']}) + .toList(); + if (suggestionsItem.isNotEmpty) { + showDateFields = false; + showArrow = false; + } + }); + print("status code : ${response.statusCode}"); + } + } + } + + Future searchSuggestionsByTag(String input) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + var url = Uri.parse("${globals.api}/tags?name=${input}"); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=$accessToken" + }); + + if (response.statusCode == 200) { + final data = json.decode(utf8.decode(response.bodyBytes)); + setState(() { + suggestionsTags = (data as List) + .map((feature) => {'name': feature['name']}) + .toList(); + if (suggestionsTags.isNotEmpty) { + showInputGeo = false; + showInputSearch = false; + showArrow = false; + } + }); + } + } + } + + Future> toggleInterested(String eventId) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + final url = Uri.parse("${globals.api}/events/${eventId}/interest"); + if (accessToken.isNotEmpty) { + final response = await http.post( + url, + headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=$accessToken" + }, + ); + + if (response.statusCode != 200) { + throw (AppLocalizations.of(context)?.toogle_interest ?? + "Error toogle interest: ${response.statusCode}"); + } + + var event = json.decode(response.body); + return event; + } + return {"interested": false, "interested_count": 0}; + } + + Future fetchPostsByLocation() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + var url = await getUrlForEvents(); + final response = await http.get(url, headers: { + "Content-Type": "application/json", + HttpHeaders.cookieHeader: "access_token=$accessToken" + }); + + print("status code : ${response.statusCode}"); + if (response.statusCode == 200) { + final List body = json.decode(utf8.decode(response.bodyBytes)); + print("results fetch : ${body}"); + print("fetch count : ${_fetchCount}"); + // Update state after getting the response + + setState(() { + int counter = filteredPosts.length; + // If we have results, map them to Events + filteredPosts = body + .map((e) => Events.fromJson(e as Map)) + .toList(); + if (counter == filteredPosts.length) { + _fetchCount--; + } + }); + } else { + throw Exception('Failed to load posts'); + } + } + } + + onTapFunctionDatePicker( + {required BuildContext context, String position = ""}) async { + DateTime dateEvent = DateTime.now(); + if (startDatepicker.text.isNotEmpty) { + dateEvent = DateTime.parse(formatDate(startDatepicker.text)); + } + + DateTime? pickedDate = await showDatePicker( + context: context, firstDate: dateEvent, lastDate: DateTime(2104)); + if (pickedDate == null) return; + if (position == "start") { + startDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate); + } else if (position == "end") { + endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate); + } + + fetchPostsByLocation(); + } + + Padding _buildDateField(String position) { + TextEditingController datePicker = startDatepicker; + String hintText = AppLocalizations.of(context)?.start_date ?? "Start date"; + if (position == "end") { + datePicker = endDatepicker; + hintText = AppLocalizations.of(context)?.end_date ?? "End date"; + } + return Padding( + padding: const EdgeInsets.all(8.0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: datePicker, + readOnly: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + suffixIcon: datePicker.text.isEmpty + ? null + : IconButton( + icon: const Icon(Icons.clear), + onPressed: () async { + setState(() { + datePicker.text = ''; + }); + fetchPostsByLocation(); + }, + ), + hintText: hintText), + onTap: () => + onTapFunctionDatePicker(context: context, position: position)), + ); + } + + Widget _buildSearchField({ + required TextEditingController controller, + required String labelText, + required Function(String) onChanged, + required Function() onClear, + required List> suggestions, + required Function(Map) onSuggestionTap, + }) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + TextField( + controller: controller, + decoration: InputDecoration( + labelText: labelText, + border: OutlineInputBorder(), + suffixIcon: controller.text.isEmpty + ? null + : IconButton( + icon: const Icon(Icons.clear), + onPressed: () => onClear(), + ), + ), + onChanged: onChanged, + ), + if (suggestions.isNotEmpty) + Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: Colors.blue), + borderRadius: BorderRadius.circular(8), + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: suggestions.length, + itemBuilder: (context, index) { + final suggestion = suggestions[index]; + return ListTile( + title: Text( + suggestion['name'] ?? suggestion['place_name'] ?? ''), + onTap: () => onSuggestionTap(suggestion), + ); + }, + ), + ), + ], + ), + ); + } + + Future popCamera() async { + await availableCameras().then((value) => Navigator.push(context, + MaterialPageRoute(builder: (_) => Camera(camera: value.first)))); + } + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + final localeProvider = Provider.of(context); + return Scaffold( + appBar: AppBar( + title: Text(loc?.menu_list ?? "Item list menu"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + drawer: MyDrawer(), + body: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!), + ), + if (showInputSearch) + _buildSearchField( + controller: inputItem, + labelText: loc?.search_item ?? "Search by item", + onChanged: (value) { + _fetchCount = 0; + if (value.isNotEmpty) { + setState(() { + itemName = value; + searchSuggestionsByItem(value); + }); + } else { + setState(() { + inputItem.clear(); + itemName = ''; + suggestionsItem.clear(); + showDateFields = true; + showArrow = true; + }); + fetchPostsByLocation(); + } + }, + onClear: () { + _fetchCount = 0; + setState(() { + inputItem.clear(); + itemName = ''; + suggestionsItem.clear(); + showDateFields = true; + showArrow = true; + }); + fetchPostsByLocation(); + }, + suggestions: suggestionsItem, + onSuggestionTap: (suggestion) async { + _fetchCount = 0; + setState(() { + itemName = suggestion['name']; + inputItem.text = itemName; + suggestionsItem.clear(); + showDateFields = true; + showArrow = true; + }); + await fetchPostsByLocation(); + }, + ), + if ((showDateFields) && (showInputTag)) + _buildSearchField( + controller: inputTags, + labelText: loc?.search_tag ?? "Search by tags", + onChanged: (value) { + _fetchCount = 0; + if (value.isNotEmpty) { + setState(() { + itemTags = value; + searchSuggestionsByTag(value); + }); + } else { + setState(() { + inputTags.clear(); + showArrow = true; + showInputSearch = true; + showInputGeo = true; + itemTags = ''; + }); + fetchPostsByLocation(); + } + }, + onClear: () { + _fetchCount = 0; + setState(() { + inputTags.clear(); + }); + fetchPostsByLocation(); + }, + suggestions: suggestionsTags, + onSuggestionTap: (suggestion) async { + _fetchCount = 0; + + setState(() { + itemTags = suggestion['name']; + inputTags.text = itemTags; + suggestionsTags.clear(); + showArrow = true; + showInputSearch = true; + showInputGeo = true; + }); + await fetchPostsByLocation(); + }, + ), + if ((showDateFields) && (showArrow)) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: _buildDateField("start")), + Flexible(child: _buildDateField("end")) + ]), + if ((showDateFields) && (showInputGeo)) + _buildSearchField( + controller: inputGeo, + labelText: + loc?.search_geographical ?? 'Search by geographical zone', + onChanged: (value) async { + _fetchCount = 0; + + if (value.isNotEmpty) { + setState(() { + geographicalZone = value; + searchSuggestionsGeo(value); + }); + } else { + final prefs = await SharedPreferences.getInstance(); + prefs.remove("city_lat"); + prefs.remove("city_long"); + setState(() { + inputGeo.clear(); + geographicalZone = ''; + suggestionsGeo.clear(); + showArrow = true; + showInputSearch = true; + showInputTag = true; + }); + fetchPostsByLocation(); + } + }, + onClear: () async { + _fetchCount = 0; + final prefs = await SharedPreferences.getInstance(); + prefs.remove("city_lat"); + prefs.remove("city_long"); + setState(() { + inputGeo.clear(); + geographicalZone = ''; + suggestionsGeo.clear(); + showArrow = true; + showInputSearch = true; + showInputTag = true; + }); + fetchPostsByLocation(); + }, + suggestions: suggestionsGeo, + onSuggestionTap: (suggestion) async { + _fetchCount = 0; + final latitude = suggestion['geometry']['coordinates'][1]; + final longitude = suggestion['geometry']['coordinates'][0]; + setState(() { + geographicalZone = suggestion['place_name']; + inputGeo.text = geographicalZone; + suggestionsGeo.clear(); + showArrow = true; + showInputSearch = true; + showInputTag = true; + }); + final prefs = await SharedPreferences.getInstance(); + prefs.setDouble("city_lat", latitude); + prefs.setDouble("city_long", longitude); + await fetchPostsByLocation(); + }, + ), + if (showArrow) + IconButton( + onPressed: () { + setState(() { + showDateFields = !showDateFields; // Toggle visibility + }); + }, + icon: Icon( + showDateFields + ? Icons.keyboard_arrow_up + : Icons.keyboard_arrow_down, + color: Colors.blue, + ), + tooltip: showDateFields + ? loc?.show_date_field ?? 'Show Date Fields' + : loc?.hide_date_field ?? 'Hide Date Fields', + ), + Expanded( + child: FutureBuilder>( + future: postsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } else if (snapshot.hasData) { + final posts = snapshot.data!; + final displayedPosts = + filteredPosts.isEmpty ? posts : filteredPosts; + return buildPosts(displayedPosts); + } else { + return Center( + child: Text(AppLocalizations.of(context)?.no_data ?? + "No data available"), + ); + } + }, + ), + ), + ], + ), + floatingActionButton: FloatingActionButton( + onPressed: popCamera, + backgroundColor: Colors.blue, + tooltip: loc?.search ?? 'Recherche', + child: const Icon(Icons.photo_camera, color: Colors.white), + ), + ); + } + + // Function to display fetched data on screen + Widget buildPosts(List posts) { + final displayedPosts = filteredPosts; + // If filteredPosts is empty, show a message saying no data is available + if (displayedPosts.isEmpty) { + return Center( + child: Text( + AppLocalizations.of(context)?.no_events ?? + 'No events available for this location.', + style: TextStyle(fontSize: 18, color: Colors.grey)), + ); + } + + return ListView.separated( + controller: _scrollController, + itemCount: displayedPosts.isNotEmpty + ? displayedPosts.length + + (_isLoading ? 1 : 0) // Add 1 only if loading + : 0, + itemBuilder: (context, index) { + if (index >= displayedPosts.length) { + return _isLoading + ? Center(child: CircularProgressIndicator()) + : SizedBox.shrink(); + } + final post = displayedPosts[index]; + final startDate = DateTime.parse(post.startDate!); + //final date = DateFormat.yMd().format(startDate); + //final time = DateFormat.Hm().format(startDate); + final locale = + Provider.of(context).locale?.toString() ?? + 'en_US'; + final dateLongue = + DateFormat('EEEE d MMMM y', locale).format(startDate); + final countInterestedString = + AppLocalizations.of(context)?.count_interested ?? + "Interested people number"; + final countInterested = + "${countInterestedString} : ${post.interestedCount}"; + return ListTile( + title: Text('${post.name!}'), + subtitle: Text('${post.place!}\n${dateLongue}\n${countInterested}'), + trailing: IconButton( + onPressed: () async { + try { + final result = await toggleInterested(post.id!); + setState(() { + post.interested = result["interested"]; + post.interestedCount = result["interested_count"]; + }); + + if (result["interested"] == true) { + NotificationService.scheduleEventNotification( + eventId: post.id!, + title: "Rappel évènement", + body: + "Ton évènement '${post.name}' commence dans 1 heure !", + eventDate: DateTime.parse(post.startDate!), + ); + } else { + NotificationService.cancel(post.id!); + } + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)?.error_update ?? + "Error when updating")), + ); + } + }, + icon: Icon( + post.interested ?? false + ? Icons.favorite + : Icons.favorite_border, + color: + post.interested ?? false ? Colors.red : Colors.grey)), + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => ItemMenu(title: post.id!)), + ); + }, + ); + }, + separatorBuilder: (context, index) { + return Divider(); + }); + } +} diff --git a/covas_mobile_new/lib/pages/LoginDemo.dart b/covas_mobile_new/lib/pages/LoginDemo.dart new file mode 100644 index 0000000..d6dd224 --- /dev/null +++ b/covas_mobile_new/lib/pages/LoginDemo.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:permission_handler/permission_handler.dart'; +import '../classes/auth_service.dart'; +import '../pages/ListItemMenu.dart'; +import '../pages/AddProfile.dart'; +import '../pages/ForgotPassword.dart'; +import '../classes/alert.dart'; +import '../classes/ad_helper.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // + +class LoginDemo extends StatefulWidget { + @override + _LoginDemoState createState() => _LoginDemoState(); +} + +class _LoginDemoState extends State with ShowAlertDialog { + BannerAd? _bannerAd; + TextEditingController inputPseudo = TextEditingController(); + TextEditingController inputPassword = TextEditingController(); + final AuthService _authService = AuthService(); + bool _rememberMe = false; + + Future _login(BuildContext context) async { + final pseudo = inputPseudo.text; + final password = inputPassword.text; + + if (pseudo.isEmpty || password.isEmpty) { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.empty_input ?? "Empty input"); + return; + } + + bool success = + await _authService.login(pseudo, password, rememberMe: _rememberMe); + + if (success) { + Navigator.push( + context, MaterialPageRoute(builder: (_) => ListItemMenu())); + } else { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.failed_auth ?? "Authentication failed"); + } + } + + @override + void initState() { + super.initState(); + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + + _checkLocationPermission(); + _checkLoginStatus(); + } + + Future _checkLoginStatus() async { + bool loggedIn = await _authService.isLoggedIn(); + if (loggedIn) { + Navigator.push( + context, MaterialPageRoute(builder: (_) => ListItemMenu())); + } + } + + Future _checkLocationPermission() async { + PermissionStatus status = await Permission.location.status; + if (!status.isGranted) { + await Permission.location.request(); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.login_page ?? "Login Page"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + body: SingleChildScrollView( + child: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 15), + child: TextField( + controller: inputPseudo, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.pseudo ?? 'Pseudo', + hintText: + AppLocalizations.of(context)?.enter_existing_pseudo ?? + 'Enter a existing pseudo', + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 15, vertical: 15), + child: TextField( + controller: inputPassword, + obscureText: true, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.password ?? "Password", + hintText: AppLocalizations.of(context)?.enter_password ?? + "Enter the password", + ), + ), + ), + CheckboxListTile( + title: Text( + AppLocalizations.of(context)?.remembr_me ?? "Remember me"), + value: _rememberMe, + onChanged: (newValue) { + setState(() { + _rememberMe = newValue ?? false; + }); + }, + ), + TextButton( + onPressed: () { + Navigator.push(context, + MaterialPageRoute(builder: (_) => PasswordForgot())); + }, + child: Text( + AppLocalizations.of(context)?.forgot_password ?? + 'Forgot Password', + style: TextStyle(color: Colors.blue, fontSize: 15)), + ), + Container( + height: 50, + width: 250, + decoration: BoxDecoration( + color: Colors.blue, borderRadius: BorderRadius.circular(20)), + child: TextButton( + onPressed: () => _login(context), + child: Text(AppLocalizations.of(context)?.sign_in ?? 'Sign in', + style: TextStyle(color: Colors.white, fontSize: 25)), + ), + ), + SizedBox(height: 130), + InkWell( + child: Text(AppLocalizations.of(context)?.new_user ?? + 'New User? Create Account'), + onTap: () { + Navigator.push( + context, MaterialPageRoute(builder: (_) => AddProfile())); + }, + ), + ], + ), + ), + ); + } +} diff --git a/covas_mobile_new/lib/pages/MapboxPages.dart b/covas_mobile_new/lib/pages/MapboxPages.dart new file mode 100644 index 0000000..549bc96 --- /dev/null +++ b/covas_mobile_new/lib/pages/MapboxPages.dart @@ -0,0 +1,452 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; // For environment variables +import 'package:flutter/services.dart'; // For loading assets +import 'package:http/http.dart' as http; +import 'package:mapbox_gl/mapbox_gl.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:geolocator/geolocator.dart'; // For getting the user's location + +import '../classes/alert.dart'; // Assuming this contains your error dialog code. +import '../variable/globals.dart' as globals; +import '../classes/MyDrawer.dart'; +import '../classes/auth_service.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // + +void main() async { + await dotenv.load(fileName: ".env"); // Load .env file + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Directions Example', + theme: ThemeData( + primarySwatch: Colors.blue, + ), + home: const MapboxPages(title: 'Event Location', place: "Flutter"), + ); + } +} + +class MapboxPages extends StatefulWidget { + const MapboxPages({Key? key, required this.title, required this.place}) + : super(key: key); + + final String title; + final String place; + + @override + State createState() => _MapboxPagesState(); +} + +class _MapboxPagesState extends State with ShowAlertDialog { + final AuthService _authService = AuthService(); + + late MapboxMapController mapController; + late String mapboxAccessToken; + List routeCoordinates = []; + String selectedMode = 'driving'; + double longitude = 0.0; + double latitude = 0.0; + bool isLoading = true; + late LatLng userPosition; + bool isUserPositionInitialized = false; + Line? currentRouteLine; + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + _getUserLocation(); + } + + void _initToken() { + mapboxAccessToken = dotenv.env['MAPBOX_ACCESS_TOKEN'] ?? ''; + if (mapboxAccessToken.isEmpty) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.map_token ?? + "Map Access Token is not available."); + } + } + + Future _getEventInfo() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isNotEmpty) { + var urlGet = Uri.parse("${globals.api}/events/${widget.title}"); + + var responseGet = await http.get(urlGet, + headers: {HttpHeaders.cookieHeader: 'access_token=${accessToken}'}); + if (responseGet.statusCode == 200) { + var events = jsonDecode(utf8.decode(responseGet.bodyBytes)); + latitude = events["latitude"]; + longitude = events["longitude"]; + + setState(() { + isLoading = false; + }); + } else { + _handleErrorResponse(responseGet.statusCode); + } + } else { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.invalid_cache ?? "Invalid cache."); + } + } + + void _handleErrorResponse(int statusCode) { + final messages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + + final errorMessage = messages[statusCode] ?? + AppLocalizations.of(context)?.unknown_error_auth ?? + "Unknown error auth"; + showAlertDialog( + context, AppLocalizations.of(context)?.error ?? "Error", errorMessage); + } + + Future _getUserLocation() async { + try { + bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.geo_disabled ?? + "Location services are disabled."); + return; + } + + LocationPermission permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.permission_denied ?? + "Location permissions are denied."); + return; + } + } + + if (permission == LocationPermission.deniedForever) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.enable_permission ?? + "Location permissions are permanently denied. Enable them in settings."); + return; + } + const LocationSettings locationSettings = LocationSettings( + accuracy: LocationAccuracy.medium, timeLimit: Duration(seconds: 5)); + Position? position; + try { + position = await Geolocator.getCurrentPosition( + locationSettings: locationSettings); + } on LocationServiceDisabledException { + // Handle location services disabled + position = await Geolocator.getLastKnownPosition(); + if (position == null) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.no_last_position ?? + "No last known position available.."); + } + } catch (e) { + // Handle other errors + position = await Geolocator.getLastKnownPosition(); + if (position == null) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.no_last_position ?? + "No last known position available"); + } + } + if (position != null) { + setState(() { + userPosition = LatLng(position!.latitude, position!.longitude); + isUserPositionInitialized = true; + }); + } + _initToken(); + _getEventInfo(); + } catch (e) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.failed_location ?? + "Failed to get user location"); + } + } + + Future _fetchRoute( + LatLng origin, LatLng destination, String mode) async { + final url = Uri.parse( + 'https://api.mapbox.com/directions/v5/mapbox/$mode/${origin.longitude},${origin.latitude};${destination.longitude},${destination.latitude}?geometries=geojson&access_token=$mapboxAccessToken', + ); + + final response = await http.get(url); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final geometry = data['routes'][0]['geometry']['coordinates']; + setState(() { + routeCoordinates = geometry.map((coord) { + return LatLng(coord[1], coord[0]); + }).toList(); + }); + } else { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.failed_fetch ?? + "Failed to fetch the route"); + } + } + + // Called when the map is created + void _onStyleLoaded() async { + // Log the map controller and coordinates + + // Check if the mapController is really initialized + if (mapController != null) { + try { + // Ensure the coordinates are valid + if (latitude != 0.0 && longitude != 0.0) { + // Load marker image as Uint8List + final userMarkerImage = await _loadMarkerImage('images/marker.png'); + + // Register the image with Mapbox + await mapController.addImage('event-marker', userMarkerImage); + + final symbolOptions = SymbolOptions( + geometry: LatLng(latitude, longitude), + iconImage: "event-marker", // Use the registered custom marker + iconSize: 0.4, // Optional: Adjust size + ); + + // Debugging symbol options + + // Add symbol to map + mapController!.addSymbol(symbolOptions); + } else { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.invalid_coordinates_symbol ?? + "Error: Invalid coordinates, cannot add symbol."); + } + } catch (e) { + // Handle any exception that occurs when adding the symbol + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.error_symbol ?? + "Error when adding symbol."); + } + } else { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.error_symbol ?? + "Error when adding symbol."); + } + } + + Future _drawRouteAndMarkers() async { + // Remove previous route line if it exists + if (currentRouteLine != null) { + await mapController.removeLine(currentRouteLine!); + currentRouteLine = null; + } + if (!isUserPositionInitialized) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.position_not_init ?? + "User position is not yet initialized. Try again."); + return; + } + + if (mapController != null && + userPosition != null && + latitude != 0.0 && + longitude != 0.0) { + final destination = LatLng(latitude, longitude); + + // Register the custom images + // Add event marker + + final eventMarkerImage = await _loadMarkerImage('images/marker-red.png'); + + // Register the image with Mapbox + await mapController.addImage('user-marker', eventMarkerImage); + await mapController.addSymbol(SymbolOptions( + geometry: userPosition, + iconImage: 'user-marker', // Custom icon for event + iconSize: 0.2, + )); + + // Fetch and draw route + await _fetchRoute(userPosition, destination, selectedMode); + + if (routeCoordinates.isNotEmpty) { + currentRouteLine = await mapController.addLine( + LineOptions( + geometry: routeCoordinates, + lineColor: '#3b9ddd', + lineWidth: 5.0, + lineOpacity: 0.8, + ), + ); + + _zoomToFitRoute(routeCoordinates); + } + } else { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.invalid_coordinates ?? + "Invalid coordinates or user position."); + } + } + + void _zoomToFitRoute(List coordinates) { + // Calculate the bounding box + double minLat = coordinates.first.latitude; + double maxLat = coordinates.first.latitude; + double minLng = coordinates.first.longitude; + double maxLng = coordinates.first.longitude; + + for (LatLng coord in coordinates) { + if (coord.latitude < minLat) minLat = coord.latitude; + if (coord.latitude > maxLat) maxLat = coord.latitude; + if (coord.longitude < minLng) minLng = coord.longitude; + if (coord.longitude > maxLng) maxLng = coord.longitude; + } + + // Define the bounds + LatLng southwest = LatLng(minLat, minLng); + LatLng northeast = LatLng(maxLat, maxLng); + + mapController.moveCamera( + CameraUpdate.newLatLngBounds( + LatLngBounds(southwest: southwest, northeast: northeast), + left: 50, // Padding on the left + top: 50, // Padding on the top + right: 50, // Padding on the right + bottom: 50, // Padding on the bottom + ), + ); + } + + // Load image from assets + Future _loadMarkerImage(String assetPath) async { + final ByteData data = await rootBundle.load(assetPath); + return data.buffer.asUint8List(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.place), + actions: [ + DropdownButton( + value: selectedMode, + items: [ + DropdownMenuItem( + value: 'walking', + child: Row( + children: [ + Icon(Icons.directions_walk, color: Colors.blue), + SizedBox(width: 8), + Text(AppLocalizations.of(context)?.walking ?? 'Walking'), + ], + ), + ), + DropdownMenuItem( + value: 'cycling', + child: Row( + children: [ + Icon(Icons.directions_bike, color: Colors.green), + SizedBox(width: 8), + Text(AppLocalizations.of(context)?.cycling ?? 'Cycling'), + ], + ), + ), + DropdownMenuItem( + value: 'driving', + child: Row( + children: [ + Icon(Icons.directions_car, color: Colors.red), + SizedBox(width: 8), + Text(AppLocalizations.of(context)?.driving ?? 'Driving'), + ], + ), + ), + ], + onChanged: (mode) { + setState(() { + selectedMode = mode!; + }); + }, + ) + ], + ), + drawer: MyDrawer(), + body: Stack( + children: [ + isLoading + ? Center(child: CircularProgressIndicator()) + : MapboxMap( + accessToken: mapboxAccessToken, + onMapCreated: (controller) { + mapController = controller; + }, + onStyleLoadedCallback: _onStyleLoaded, + initialCameraPosition: CameraPosition( + target: LatLng(latitude, longitude), + zoom: 14.0, + ), + ), + Positioned( + bottom: 20, + right: 20, + child: FloatingActionButton( + onPressed: _drawRouteAndMarkers, + child: Icon(Icons.directions), + tooltip: AppLocalizations.of(context)?.get_direction ?? + 'Get Directions and Markers', + ), + ), + ], + ), + ); + } +} diff --git a/covas_mobile_new/lib/pages/UpdateEventImage.dart b/covas_mobile_new/lib/pages/UpdateEventImage.dart new file mode 100644 index 0000000..0a08e25 --- /dev/null +++ b/covas_mobile_new/lib/pages/UpdateEventImage.dart @@ -0,0 +1,815 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:http/http.dart' as http; +import 'package:uuid/uuid.dart'; +import 'package:intl/intl.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:textfield_tags/textfield_tags.dart'; +import '../classes/MyDrawer.dart'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'ItemMenu.dart'; +import '../classes/alert.dart'; +import '../classes/eventAdded.dart'; + +import '../variable/globals.dart' as globals; + +import '../classes/ad_helper.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import '../classes/auth_service.dart'; + +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import '../locale_provider.dart'; // + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await MobileAds.instance.initialize(); + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + Map events = {}; + String imagePath = ""; + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + home: UpdateeventImage(events: events, imagePath: imagePath), + ); + } +} + +class UpdateeventImage extends StatefulWidget { + const UpdateeventImage( + {Key? key, required this.events, required this.imagePath}) + : super(key: key); + final Map events; + final String imagePath; + + @override + _UpdateeventImageState createState() => _UpdateeventImageState(); +} + +class _UpdateeventImageState extends State + with ShowAlertDialog, ShowEventDialog { + BannerAd? _bannerAd; + final AuthService _authService = AuthService(); + + TextEditingController inputName = TextEditingController(); + + TextEditingController inputDate = TextEditingController(); + TextEditingController inputDesc = TextEditingController(); + + TextEditingController inputGeo = TextEditingController(); + + TextEditingController startDatepicker = TextEditingController(); + TextEditingController startTimepicker = TextEditingController(); + TextEditingController endDatepicker = TextEditingController(); + TextEditingController endTimepicker = TextEditingController(); + final _stringTagController = StringTagController(); + + List> suggestions = []; + String geographicalZone = ""; + + List initialTags = []; + + final _stringOrgaController = StringTagController(); + List initialOrga = []; + + onTapFunctionDatePicker( + {required BuildContext context, required String position}) async { + String date = "start_date"; + if (position == "end") { + date = "end_date"; + } + DateTime dateEvent; + if (widget.events[date].toString().isEmpty) { + dateEvent = DateTime.now(); + } else { + dateEvent = DateTime.parse(widget.events[date]); + } + DateTime? pickedDate = await showDatePicker( + context: context, + firstDate: dateEvent, + initialDate: dateEvent, + lastDate: DateTime(2104)); + if (pickedDate == null) return; + if (position == "start") { + startDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate); + } + if (position == "end") { + endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedDate); + } + } + + onTapFunctionTimePicker( + {required BuildContext context, required String position}) async { + String date = "start_date"; + if (position == "end") { + date = "end_date"; + } + TimeOfDay timeEvent; + if (widget.events[date].toString().isEmpty) { + timeEvent = TimeOfDay.now(); + } else { + timeEvent = TimeOfDay.fromDateTime(DateTime.parse(widget.events[date])); + } + TimeOfDay? pickedDate = + await showTimePicker(context: context, initialTime: timeEvent); + if (pickedDate == null) return; + if (position == "start") { + startTimepicker.text = pickedDate.format(context); + } + if (position == "end") { + endTimepicker.text = pickedDate.format(context); + } + } + + convertNulltoEmptyString(var check) { + if (check == null) { + return ""; + } + return check; + } + + convertNulltoArray(List check) { + if (check == null) { + return []; + } + return check; + } + + String formatDate(String date) { + var splitedDate = date.split("-"); + var day = splitedDate[0]; + var month = splitedDate[1]; + var year = splitedDate[2]; + + return "${year}-${month}-${day}"; + } + + Future _updateEvent(BuildContext context) async { + // Gather inputs + var name = inputName.text; + var place = inputGeo.text; + var description = inputDesc.text; + List tags = List.from(_stringTagController.getTags as List); + List organizers = + List.from(_stringOrgaController.getTags as List); + + var startDateFormat = formatDate(startDatepicker.text); + DateTime startDateCompare = DateTime.parse(startDateFormat); + DateTime dateNow = DateTime.now(); + var endDateFormat = formatDate(endDatepicker.text); + var startDate = + "${startDateFormat}T${startTimepicker.text.replaceAll('-', ':')}"; + var endDate = "${endDateFormat}T${endTimepicker.text.replaceAll('-', ':')}"; + + if (!startDateCompare.isAfter(dateNow)) { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.no_future_event ?? "No future event"); + return; + } + + SharedPreferences prefs = await SharedPreferences.getInstance(); + var accessToken = prefs.getString("access_token") ?? ""; + + if (accessToken.isEmpty) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.missing_token ?? + "Missing access token"); + return; + } + + try { + await dotenv.load(); + final ApiTokenGoogle = dotenv.env['PLACE_API_KEY'] ?? ''; + // Searchbox API for geocoding the place (No session token) + final searchboxUrl = Uri.parse( + 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${place}&key=${ApiTokenGoogle}'); + + // Perform the request + final searchboxResponse = await http.get(searchboxUrl); + + if (searchboxResponse.statusCode != 200) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.geocoding_error ?? + "Error when geocoding"); + return; + } + + final searchboxData = json.decode(searchboxResponse.body); + if (searchboxData['results'].isEmpty) { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.no_found_place ?? "No found place"); + return; + } + + // Extract place details from the searchbox response + final firstFeature = searchboxData['results'][0]; + place = firstFeature["formatted_address"]; + final coordinates = firstFeature['geometry']['location']; + final longitude = coordinates["lng"]; + final latitude = coordinates["lat"]; + + // Check if a similar event exists + final eventsUrl = Uri.parse( + "${globals.api}/events/search?item=$name&date_event=$startDate"); + final eventsResponse = await http.get(eventsUrl, headers: { + HttpHeaders.cookieHeader: 'access_token=$accessToken', + }); + + if (eventsResponse.statusCode == 200) { + final events = json.decode(utf8.decode(eventsResponse.bodyBytes)); + if (events.isNotEmpty) { + Navigator.push( + context, + MaterialPageRoute( + builder: (_) => ItemMenu(title: events[0]["id"]), + ), + ); + return; + } + } + + // Upload image to imgbb + final imgbbUrl = Uri.parse( + 'https://api.imgbb.com/1/upload?expiration=15552000&key=${dotenv.env["IMGBB_API_KEY"]}'); + File image = File(widget.imagePath); + Uint8List imageBytes = await image.readAsBytes(); + String base64Image = base64.encode(imageBytes); + + final imgbbRequest = http.MultipartRequest('POST', imgbbUrl) + ..fields['image'] = base64Image; + final imgbbResponse = + await http.Response.fromStream(await imgbbRequest.send()); + + if (imgbbResponse.statusCode != 200) { + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.upload_error ?? + "Error when image uploading"); + return; + } + + final imgbbData = json.decode(imgbbResponse.body); + final imgUrl = imgbbData['data']['url']; + + // Create or update the event + final eventUrl = Uri.parse("${globals.api}/events"); + final eventResponse = await http.put( + eventUrl, + headers: { + HttpHeaders.cookieHeader: 'access_token=$accessToken', + HttpHeaders.acceptHeader: 'application/json, text/plain, */*', + HttpHeaders.contentTypeHeader: 'application/json', + }, + body: jsonEncode({ + 'name': name, + 'place': place, + 'start_date': startDate, + 'end_date': endDate, + 'organizers': organizers, + 'latitude': latitude, + 'longitude': longitude, + 'description': description, + 'imgUrl': imgUrl, + 'tags': tags, + }), + ); + + if (eventResponse.statusCode == 200 || eventResponse.statusCode == 201) { + String event_message = + AppLocalizations.of(context)?.event_added ?? "Event added"; + showEventDialog(context, "$event_message : $name"); + } else { + handleHttpError(eventResponse.statusCode, context); + } + } catch (e) { + showAlertDialog(context, AppLocalizations.of(context)?.error ?? "Error", + AppLocalizations.of(context)?.app_error ?? "Error application"); + } + } + +// Utility function to handle HTTP errors + void handleHttpError(int statusCode, BuildContext context) { + final messages = { + 400: AppLocalizations.of(context)?.request_error ?? + "Poorly constructed query", + 406: AppLocalizations.of(context)?.incorrect_password ?? + "Incorrect password", + 404: AppLocalizations.of(context)?.unknown_user ?? "Unknown user", + 403: AppLocalizations.of(context)?.disabled_user ?? "Disabled user", + 410: AppLocalizations.of(context)?.invalid_token ?? "Invalid token", + 500: AppLocalizations.of(context)?.internal_error_server ?? + "Internal error server" + }; + showAlertDialog( + context, + AppLocalizations.of(context)?.error ?? "Error", + messages[statusCode] ?? + AppLocalizations.of(context)?.unknown_error ?? + "Unknown error"); + } + + void start() async { + print("events : ${widget.events}"); + inputName.text = convertNulltoEmptyString(widget.events["name"]); + inputGeo.text = convertNulltoEmptyString(widget.events["place"]); + inputDesc.text = convertNulltoEmptyString(widget.events["description"]); + if (widget.events["start_date"].toString().isNotEmpty) { + DateTime pickedStartDate = + DateTime.parse(convertNulltoEmptyString(widget.events["start_date"])); + startDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedStartDate); + startTimepicker.text = DateFormat("HH-mm").format(pickedStartDate); + } + if (widget.events["end_date"].toString().isNotEmpty) { + DateTime pickedEndDate = + DateTime.parse(convertNulltoEmptyString(widget.events["end_date"])); + endDatepicker.text = DateFormat("dd-MM-yyyy").format(pickedEndDate); + endTimepicker.text = DateFormat("HH-mm").format(pickedEndDate); + } + initialTags = List.from(widget.events['tags'] as List); + initialOrga = List.from(widget.events['organizers'] as List); + } + + @override + void initState() { + super.initState(); + _authService.checkTokenStatus(context); + + AdHelper.createBannerAd(() => setState(() {})).then((ad) { + setState(() { + _bannerAd = ad; + }); + }); + start(); + } + + final _formKey = GlobalKey(); + String? _validateField(String? value) { + return value!.isEmpty + ? AppLocalizations.of(context)?.required_input ?? 'Required input' + : null; + } + + Future searchSuggestions(String input) async { + await dotenv.load(fileName: ".env"); // Load .env file + + final ApiTokenGoogle = dotenv.env['PLACE_API_KEY'] ?? ''; + + // Define the Searchbox API URL + final searchboxUrl = Uri.parse( + 'https://maps.googleapis.com/maps/api/place/textsearch/json?query=${input}&key=${ApiTokenGoogle}'); + + // Perform the request + final response = await http.get(searchboxUrl); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + + setState(() { + // Map the results to extract name and full_address + suggestions = (data['results'] as List) + .map((feature) => { + 'name': feature['name'], + 'formatted_address': feature[ + 'formatted_address'] // Adjusted to match the data structure + }) + .toList(); + }); + } else { + throw Exception('Failed to load suggestions'); + } + } + + Padding _buildGeographicalZoneSearchField() { + return Padding( + padding: + const EdgeInsets.only(left: 15.0, right: 15.0, top: 15, bottom: 0), + child: Column( + children: [ + TextField( + controller: inputGeo, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)?.location ?? 'Location', + border: OutlineInputBorder(), + suffixIcon: IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + setState(() { + inputGeo.clear(); // Clear the text field + geographicalZone = ''; // Reset the geographical zone state + suggestions.clear(); // Optionally clear suggestions + }); + }, + ), + ), + onChanged: (value) { + setState(() { + geographicalZone = value; + searchSuggestions(value); + }); + }, + ), + if (suggestions.isNotEmpty) + Container( + height: 200, + decoration: BoxDecoration( + border: Border.all(color: Colors.blue), + borderRadius: BorderRadius.circular(8), + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: suggestions.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(suggestions[index]['name']), + subtitle: Text(suggestions[index]['formatted_address']), + onTap: () async { + print("suggestion tapped : ${suggestions[index]}"); + + setState(() { + geographicalZone = + suggestions[index]['formatted_address']; + inputGeo.text = geographicalZone; + suggestions.clear(); + }); + }, + ); + }, + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + title: Text(AppLocalizations.of(context)?.add_event ?? + "Add or Update a event"), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + drawer: MyDrawer(), + body: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + children: [ + _bannerAd == null + ? SizedBox.shrink() + : SizedBox( + height: _bannerAd!.size.height.toDouble(), + width: _bannerAd!.size.width.toDouble(), + child: AdWidget(ad: _bannerAd!)), + Padding( + padding: const EdgeInsets.only(top: 60.0), + child: Center( + child: Container( + width: 200, + height: 150, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100.0)), + child: Image.file(File(widget.imagePath))), + ), + ), + Padding( + //padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0), + padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: inputName, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.name ?? "Name", + hintText: + AppLocalizations.of(context)?.edit_event_name ?? + "Edit event name"), + ), + ), + _buildGeographicalZoneSearchField(), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: startDatepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.start_date ?? + "Start date", + hintText: AppLocalizations.of(context)?.select_date ?? + "Click to select a date"), + onTap: () => onTapFunctionDatePicker( + context: context, position: "start")), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: startTimepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.start_time ?? + "Start time", + hintText: AppLocalizations.of(context)?.select_time ?? + "Click to select a time"), + onTap: () => onTapFunctionTimePicker( + context: context, position: "start")), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: endDatepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.end_date ?? + "End date", + hintText: AppLocalizations.of(context)?.select_date ?? + "Click to select a date"), + onTap: () => onTapFunctionDatePicker( + context: context, position: "end")), + ), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextFormField( + controller: endTimepicker, + readOnly: true, + validator: (value) => _validateField(value), + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.end_time ?? + "End time", + hintText: AppLocalizations.of(context)?.select_time ?? + "Click to select a time"), + onTap: () => onTapFunctionTimePicker( + context: context, position: "end")), + ), + TextFieldTags( + textfieldTagsController: _stringTagController, + initialTags: initialTags, + textSeparators: const [' ', ','], + validator: (String tag) { + if (_stringTagController.getTags!.contains(tag)) { + return AppLocalizations.of(context)?.already_tag ?? + 'You have already entered this tag'; + } + return null; + }, + inputFieldBuilder: (context, inputFieldValues) { + return Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + child: TextField( + controller: inputFieldValues.textEditingController, + focusNode: inputFieldValues.focusNode, + onChanged: inputFieldValues.onTagChanged, + onSubmitted: inputFieldValues.onTagSubmitted, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.tag ?? 'Tags', + hintText: inputFieldValues.tags.isNotEmpty + ? '' + : AppLocalizations.of(context)?.enter_tag ?? + "Enter tag...", + errorText: inputFieldValues.error, + prefixIcon: inputFieldValues.tags.isNotEmpty + ? SingleChildScrollView( + controller: + inputFieldValues.tagScrollController, + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 4.0, + spacing: 4.0, + children: inputFieldValues.tags + .map((String tag) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + color: Colors.blue, + ), + margin: + const EdgeInsets.symmetric( + horizontal: 5.0), + padding: + const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 5.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + child: Text( + '$tag', + style: const TextStyle( + color: Colors.white), + ), + onTap: () { + //print("$tag selected"); + }, + ), + const SizedBox(width: 4.0), + InkWell( + child: const Icon( + Icons.cancel, + size: 14.0, + color: Color.fromARGB( + 255, 233, 233, 233), + ), + onTap: () { + inputFieldValues + .onTagRemoved(tag); + }, + ) + ], + ), + ); + }).toList()), + ), + ) + : null, + ), + ), + ); + }), + TextFieldTags( + textfieldTagsController: _stringOrgaController, + initialTags: initialOrga, + textSeparators: const [','], + validator: (String tag) { + if (_stringOrgaController.getTags!.contains(tag)) { + return AppLocalizations.of(context) + ?.already_organiser ?? + 'You have already entered this organizer'; + } + return null; + }, + inputFieldBuilder: (context, inputFieldValues) { + return Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + child: TextField( + controller: inputFieldValues.textEditingController, + focusNode: inputFieldValues.focusNode, + onChanged: inputFieldValues.onTagChanged, + onSubmitted: inputFieldValues.onTagSubmitted, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: + AppLocalizations.of(context)?.organizer ?? + 'Organizers', + hintText: inputFieldValues.tags.isNotEmpty + ? '' + : AppLocalizations.of(context) + ?.enter_organizer ?? + "Enter un organisateur...", + errorText: inputFieldValues.error, + prefixIcon: inputFieldValues.tags.isNotEmpty + ? SingleChildScrollView( + controller: + inputFieldValues.tagScrollController, + scrollDirection: Axis.vertical, + child: Padding( + padding: const EdgeInsets.only( + top: 8, + bottom: 8, + left: 8, + ), + child: Wrap( + runSpacing: 4.0, + spacing: 4.0, + children: inputFieldValues.tags + .map((String tag) { + return Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.0), + ), + color: Colors.blue, + ), + margin: + const EdgeInsets.symmetric( + horizontal: 5.0), + padding: + const EdgeInsets.symmetric( + horizontal: 10.0, + vertical: 5.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + child: Text( + '$tag', + style: const TextStyle( + color: Colors.white), + ), + onTap: () { + //print("$tag selected"); + }, + ), + const SizedBox(width: 4.0), + InkWell( + child: const Icon( + Icons.cancel, + size: 14.0, + color: Color.fromARGB( + 255, 233, 233, 233), + ), + onTap: () { + inputFieldValues + .onTagRemoved(tag); + }, + ) + ], + ), + ); + }).toList()), + ), + ) + : null, + ), + ), + ); + }), + Padding( + padding: const EdgeInsets.only( + left: 15.0, right: 15.0, top: 15, bottom: 0), + //padding: EdgeInsets.symmetric(horizontal: 15), + child: TextField( + controller: inputDesc, + keyboardType: TextInputType.multiline, + maxLines: 10, + decoration: InputDecoration( + border: OutlineInputBorder(), + labelText: AppLocalizations.of(context)?.description ?? + 'Description', + hintText: + AppLocalizations.of(context)?.describe_event ?? + 'Describe the event'), + ), + ), + SizedBox( + height: 30, + ), + Container( + height: 50, + width: 250, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(20)), + child: TextButton( + onPressed: () { + if (_formKey.currentState!.validate()) { + _updateEvent(context); + } + }, + child: Text( + AppLocalizations.of(context)?.add_event ?? 'Add', + style: TextStyle(color: Colors.white, fontSize: 25), + ), + ), + ) + ], + ), + ), + )); + } +} diff --git a/covas_mobile_new/lib/variable/globals.dart b/covas_mobile_new/lib/variable/globals.dart new file mode 100644 index 0000000..f90fa4d --- /dev/null +++ b/covas_mobile_new/lib/variable/globals.dart @@ -0,0 +1 @@ +String api = "https://backend.valczeryba.ovh"; diff --git a/covas_mobile_new/linux/.gitignore b/covas_mobile_new/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/covas_mobile_new/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/covas_mobile_new/linux/CMakeLists.txt b/covas_mobile_new/linux/CMakeLists.txt new file mode 100644 index 0000000..01cd2c2 --- /dev/null +++ b/covas_mobile_new/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 "covas_mobile_new") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.covas_mobile_new") + +# 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/covas_mobile_new/linux/flutter/CMakeLists.txt b/covas_mobile_new/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/linux/flutter/generated_plugin_registrant.cc b/covas_mobile_new/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..7299b5c --- /dev/null +++ b/covas_mobile_new/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,19 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#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) 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/covas_mobile_new/linux/flutter/generated_plugin_registrant.h b/covas_mobile_new/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/linux/flutter/generated_plugins.cmake b/covas_mobile_new/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..786ff5c --- /dev/null +++ b/covas_mobile_new/linux/flutter/generated_plugins.cmake @@ -0,0 +1,25 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + 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/covas_mobile_new/linux/runner/CMakeLists.txt b/covas_mobile_new/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/linux/runner/main.cc b/covas_mobile_new/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/linux/runner/my_application.cc b/covas_mobile_new/linux/runner/my_application.cc new file mode 100644 index 0000000..c3078e1 --- /dev/null +++ b/covas_mobile_new/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, "covas_mobile_new"); + 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, "covas_mobile_new"); + } + + 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/covas_mobile_new/linux/runner/my_application.h b/covas_mobile_new/linux/runner/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/macos/.gitignore b/covas_mobile_new/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/covas_mobile_new/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/covas_mobile_new/macos/Flutter/Flutter-Debug.xcconfig b/covas_mobile_new/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/covas_mobile_new/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/covas_mobile_new/macos/Flutter/Flutter-Release.xcconfig b/covas_mobile_new/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/covas_mobile_new/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/covas_mobile_new/macos/Flutter/GeneratedPluginRegistrant.swift b/covas_mobile_new/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..c21fe97 --- /dev/null +++ b/covas_mobile_new/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,24 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import file_selector_macos +import flutter_local_notifications +import geolocator_apple +import path_provider_foundation +import shared_preferences_foundation +import url_launcher_macos +import webview_flutter_wkwebview + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) + GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "WebViewFlutterPlugin")) +} diff --git a/covas_mobile_new/macos/Runner.xcodeproj/project.pbxproj b/covas_mobile_new/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..ecfec27 --- /dev/null +++ b/covas_mobile_new/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* covas_mobile_new.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "covas_mobile_new.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* covas_mobile_new.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* covas_mobile_new.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/covas_mobile_new.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/covas_mobile_new"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/covas_mobile_new.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/covas_mobile_new"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/covas_mobile_new.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/covas_mobile_new"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/covas_mobile_new/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/covas_mobile_new/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/covas_mobile_new/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/covas_mobile_new/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/covas_mobile_new/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..885bc64 --- /dev/null +++ b/covas_mobile_new/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/covas_mobile_new/macos/Runner.xcworkspace/contents.xcworkspacedata b/covas_mobile_new/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/covas_mobile_new/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/covas_mobile_new/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/covas_mobile_new/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/covas_mobile_new/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/covas_mobile_new/macos/Runner/AppDelegate.swift b/covas_mobile_new/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/covas_mobile_new/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/covas_mobile_new/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/covas_mobile_new/macos/Runner/Base.lproj/MainMenu.xib b/covas_mobile_new/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/covas_mobile_new/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/covas_mobile_new/macos/Runner/Configs/AppInfo.xcconfig b/covas_mobile_new/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..ef8d611 --- /dev/null +++ b/covas_mobile_new/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = covas_mobile_new + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.covasMobileNew + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/covas_mobile_new/macos/Runner/Configs/Debug.xcconfig b/covas_mobile_new/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/covas_mobile_new/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/covas_mobile_new/macos/Runner/Configs/Release.xcconfig b/covas_mobile_new/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/covas_mobile_new/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/covas_mobile_new/macos/Runner/Configs/Warnings.xcconfig b/covas_mobile_new/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/covas_mobile_new/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/covas_mobile_new/macos/Runner/DebugProfile.entitlements b/covas_mobile_new/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/macos/Runner/Info.plist b/covas_mobile_new/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/covas_mobile_new/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/covas_mobile_new/macos/Runner/MainFlutterWindow.swift b/covas_mobile_new/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/covas_mobile_new/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/covas_mobile_new/macos/Runner/Release.entitlements b/covas_mobile_new/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/covas_mobile_new/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/covas_mobile_new/macos/RunnerTests/RunnerTests.swift b/covas_mobile_new/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/covas_mobile_new/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/covas_mobile_new/pubspec.lock b/covas_mobile_new/pubspec.lock new file mode 100644 index 0000000..efb6d40 --- /dev/null +++ b/covas_mobile_new/pubspec.lock @@ -0,0 +1,1111 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: "9a8f69025044eb466b9b60ef3bc3ac99b4dc6c158ae9c56d25eeccf5bc56d024" + url: "https://pub.dev" + source: hosted + version: "1.6.5" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + camera: + dependency: "direct main" + description: + name: camera + sha256: d6ec2cbdbe2fa8f5e0d07d8c06368fe4effa985a4a5ddade9cc58a8cd849557d + url: "https://pub.dev" + source: hosted + version: "0.11.2" + camera_android_camerax: + dependency: transitive + description: + name: camera_android_camerax + sha256: "2d438248554f44766bf9ea34c117a5bb0074e241342ef7c22c768fb431335234" + url: "https://pub.dev" + source: hosted + version: "0.6.21" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "951ef122d01ebba68b7a54bfe294e8b25585635a90465c311b2f875ae72c412f" + url: "https://pub.dev" + source: hosted + version: "0.9.21+2" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2" + url: "https://pub.dev" + source: hosted + version: "2.10.0" + camera_web: + dependency: "direct main" + description: + name: camera_web + sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f" + url: "https://pub.dev" + source: hosted + version: "0.3.5" + characters: + dependency: transitive + description: + name: characters + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + date_format_field: + dependency: "direct main" + description: + name: date_format_field + sha256: d07a428bd253454ff3123f2e57511cf6ec101d1135af3b79cebb40e1bee8bab8 + url: "https://pub.dev" + source: hosted + version: "0.1.0" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" + devtools_shared: + dependency: transitive + description: + name: devtools_shared + sha256: "659e2d65aa5ef5c3551163811c5c6fa1b973b3df80d8cac6f618035edcdc1096" + url: "https://pub.dev" + source: hosted + version: "11.2.1" + dio: + dependency: transitive + description: + name: dio + sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + url: "https://pub.dev" + source: hosted + version: "5.9.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + dtd: + dependency: transitive + description: + name: dtd + sha256: "14a0360d898ded87c3d99591fc386b8a6ea5d432927bee709b22130cd25b993a" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2" + url: "https://pub.dev" + source: hosted + version: "5.0.3" + encrypt_shared_preferences: + dependency: "direct main" + description: + name: encrypt_shared_preferences + sha256: "9be57e1f224d6f4353bdfa79de16b364ec7dddf38840c3288c547f262e50bbad" + url: "https://pub.dev" + source: hosted + version: "0.8.9" + extension_discovery: + dependency: transitive + description: + name: extension_discovery + sha256: de1fce715ab013cdfb00befc3bdf0914bea5e409c3a567b7f8f144bc061611a7 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: transitive + description: + name: ffi + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" + url: "https://pub.dev" + source: hosted + version: "0.9.4+4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" + flutter_gemini: + dependency: "direct main" + description: + name: flutter_gemini + sha256: "993765fafb595e5d32153f393b9c5e71f853caa5bef123d292c21f672d2b9675" + url: "https://pub.dev" + source: hosted + version: "2.0.5" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + url: "https://pub.dev" + source: hosted + version: "17.2.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 + url: "https://pub.dev" + source: hosted + version: "2.0.30" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2 + url: "https://pub.dev" + source: hosted + version: "13.0.4" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d + url: "https://pub.dev" + source: hosted + version: "4.6.2" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + url: "https://pub.dev" + source: hosted + version: "2.3.13" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" + url: "https://pub.dev" + source: hosted + version: "4.2.6" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 + url: "https://pub.dev" + source: hosted + version: "4.1.3" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" + url: "https://pub.dev" + source: hosted + version: "0.2.5" + google_mobile_ads: + dependency: "direct main" + description: + name: google_mobile_ads + sha256: "0d4a3744b5e8ed1b8be6a1b452d309f811688855a497c6113fc4400f922db603" + url: "https://pub.dev" + source: hosted + version: "5.3.1" + http: + dependency: "direct main" + description: + name: http + sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "736eb56a911cf24d1859315ad09ddec0b66104bc41a7f8c5b96b4e2620cf5041" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" + url: "https://pub.dev" + source: hosted + version: "0.8.13+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "40c2a6a0da15556dc0f8e38a3246064a971a9f512386c3339b89f76db87269b6" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e + url: "https://pub.dev" + source: hosted + version: "0.8.13" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "9f143b0dba3e459553209e20cc425c9801af48e6dfa4f01a0fcf927be3f41665" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + sha256: "246b321532f0e8e2ba474b4d757eaa558ae4fdd0688fdbc1e1ca9705f9b8ca0e" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "8dcda04c3fc16c14f48a7bb586d4be1f0d1572731b6d81d51772ef47c02081e0" + url: "https://pub.dev" + source: hosted + version: "11.0.1" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" + source: hosted + version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + url: "https://pub.dev" + source: hosted + version: "1.0.6" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" + url: "https://pub.dev" + source: hosted + version: "2.2.18" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + url: "https://pub.dev" + source: hosted + version: "7.0.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" + url: "https://pub.dev" + source: hosted + version: "6.1.5+1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 + url: "https://pub.dev" + source: hosted + version: "2.4.12" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + 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" + sse: + dependency: transitive + description: + name: sse + sha256: fcc97470240bb37377f298e2bd816f09fd7216c07928641c0560719f50603643 + url: "https://pub.dev" + source: hosted + version: "4.1.8" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + url: "https://pub.dev" + source: hosted + version: "0.7.6" + textfield_tags: + dependency: "direct main" + description: + name: textfield_tags + sha256: d1f2204114157a1296bb97c20d7f8c8c7fd036212812afb2e19de7bb34acc55b + url: "https://pub.dev" + source: hosted + version: "3.0.1" + timezone: + dependency: "direct main" + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + unified_analytics: + dependency: transitive + description: + name: unified_analytics + sha256: "8d1429a4b27320a9c4fc854287d18c8fde1549bf622165c5837202a9f370b53d" + url: "https://pub.dev" + source: hosted + version: "8.0.5" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "69ee86740f2847b9a4ba6cffa74ed12ce500bbe2b07f3dc1e643439da60637b7" + url: "https://pub.dev" + source: hosted + version: "6.3.18" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.dev" + source: hosted + version: "6.3.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + url: "https://pub.dev" + source: hosted + version: "15.0.2" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + webview_flutter: + dependency: transitive + 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" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.dev" + source: hosted + version: "6.6.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" + yaml_edit: + dependency: transitive + description: + name: yaml_edit + sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 + url: "https://pub.dev" + source: hosted + version: "2.2.2" +sdks: + dart: ">=3.8.1 <4.0.0" + flutter: ">=3.32.8" diff --git a/covas_mobile_new/pubspec.yaml b/covas_mobile_new/pubspec.yaml new file mode 100644 index 0000000..6c86a73 --- /dev/null +++ b/covas_mobile_new/pubspec.yaml @@ -0,0 +1,117 @@ +name: covas_mobile +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.17.6 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + flutter_local_notifications: ^17.2.0 + timezone: ^0.9.4 + cupertino_icons: ^1.0.2 + http: ^1.2.1 + shared_preferences: ^2.2.3 + intl: ^0.20.2 + camera: ^0.11.0+1 + camera_web: ^0.3.3 + path_provider: ^2.1.3 + path: ^1.9.0 + flutter_gemini: ^2.0.4 + flutter_dotenv: ^5.1.0 + image_picker: ^1.1.2 + date_format_field: ^0.1.0 + textfield_tags: ^3.0.1 + geolocator: ^13.0.1 + permission_handler: ^11.3.1 + url_launcher: ^6.3.1 + google_mobile_ads: ^5.3.1 + encrypt_shared_preferences: ^0.8.8 + provider: ^6.1.2 # ou la dernière version + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + generate: true + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - images/flutter.png + - .env + - images/search.png + - images/marker.png + - images/marker-red.png + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/covas_mobile_new/test/widget_test.dart b/covas_mobile_new/test/widget_test.dart new file mode 100644 index 0000000..bcf0608 --- /dev/null +++ b/covas_mobile_new/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:covas_mobile_new/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/covas_mobile_new/web/favicon.png b/covas_mobile_new/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/covas_mobile_new/web/favicon.png differ diff --git a/covas_mobile_new/web/icons/Icon-192.png b/covas_mobile_new/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/covas_mobile_new/web/icons/Icon-192.png differ diff --git a/covas_mobile_new/web/icons/Icon-512.png b/covas_mobile_new/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/covas_mobile_new/web/icons/Icon-512.png differ diff --git a/covas_mobile_new/web/icons/Icon-maskable-192.png b/covas_mobile_new/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/covas_mobile_new/web/icons/Icon-maskable-192.png differ diff --git a/covas_mobile_new/web/icons/Icon-maskable-512.png b/covas_mobile_new/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/covas_mobile_new/web/icons/Icon-maskable-512.png differ diff --git a/covas_mobile_new/web/index.html b/covas_mobile_new/web/index.html new file mode 100644 index 0000000..5a3c0f3 --- /dev/null +++ b/covas_mobile_new/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + covas_mobile_new + + + + + + diff --git a/covas_mobile_new/web/manifest.json b/covas_mobile_new/web/manifest.json new file mode 100644 index 0000000..2c93bd6 --- /dev/null +++ b/covas_mobile_new/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "covas_mobile_new", + "short_name": "covas_mobile_new", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/covas_mobile_new/windows/.gitignore b/covas_mobile_new/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/CMakeLists.txt b/covas_mobile_new/windows/CMakeLists.txt new file mode 100644 index 0000000..ddf4ff7 --- /dev/null +++ b/covas_mobile_new/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(covas_mobile_new 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 "covas_mobile_new") + +# 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/covas_mobile_new/windows/flutter/CMakeLists.txt b/covas_mobile_new/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/flutter/generated_plugin_registrant.cc b/covas_mobile_new/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..b2cbd25 --- /dev/null +++ b/covas_mobile_new/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + GeolocatorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("GeolocatorWindows")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/covas_mobile_new/windows/flutter/generated_plugin_registrant.h b/covas_mobile_new/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/flutter/generated_plugins.cmake b/covas_mobile_new/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..92c9a0d --- /dev/null +++ b/covas_mobile_new/windows/flutter/generated_plugins.cmake @@ -0,0 +1,27 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows + geolocator_windows + permission_handler_windows + 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/covas_mobile_new/windows/runner/CMakeLists.txt b/covas_mobile_new/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/runner/Runner.rc b/covas_mobile_new/windows/runner/Runner.rc new file mode 100644 index 0000000..88c9d3d --- /dev/null +++ b/covas_mobile_new/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "covas_mobile_new" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "covas_mobile_new" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "covas_mobile_new.exe" "\0" + VALUE "ProductName", "covas_mobile_new" "\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/covas_mobile_new/windows/runner/flutter_window.cpp b/covas_mobile_new/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/runner/flutter_window.h b/covas_mobile_new/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/runner/main.cpp b/covas_mobile_new/windows/runner/main.cpp new file mode 100644 index 0000000..615f2cc --- /dev/null +++ b/covas_mobile_new/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"covas_mobile_new", 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/covas_mobile_new/windows/runner/resource.h b/covas_mobile_new/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/runner/resources/app_icon.ico b/covas_mobile_new/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/covas_mobile_new/windows/runner/resources/app_icon.ico differ diff --git a/covas_mobile_new/windows/runner/runner.exe.manifest b/covas_mobile_new/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/covas_mobile_new/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/covas_mobile_new/windows/runner/utils.cpp b/covas_mobile_new/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/runner/utils.h b/covas_mobile_new/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/runner/win32_window.cpp b/covas_mobile_new/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/covas_mobile_new/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/covas_mobile_new/windows/runner/win32_window.h b/covas_mobile_new/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/covas_mobile_new/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_