commit 655f8a036af7cd38864619686f0762c87b05772b Author: mustard Date: Mon Aug 26 00:34:20 2024 +0200 Initial (redacted) commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a25f838 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +.obsidian/ + +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode +app/untranslated_messages.json +app/tests.json +public/docs/* diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md new file mode 100644 index 0000000..7a4a164 --- /dev/null +++ b/.gitlab/issue_templates/Bug.md @@ -0,0 +1,24 @@ +### Summary + + + +### Steps to reproduce + + + +### What is the current _bug_ behavior? + + + +### What is the expected _correct_ behavior? + + + +### Relevant logs and/or screenshots + + + +### Possible fixes + + diff --git a/.gitlab/issue_templates/Feature.md b/.gitlab/issue_templates/Feature.md new file mode 100644 index 0000000..93f7e46 --- /dev/null +++ b/.gitlab/issue_templates/Feature.md @@ -0,0 +1,22 @@ +## :rocket: Description +### Problem to solve + + + +### Proposal + + + +### Further details + + + +## :art: Wireframe + +## :link: Links + +## :white_check_mark: Acceptance Criteria +- [ ] + + + diff --git a/.gitlab/issue_templates/Refactoring.md b/.gitlab/issue_templates/Refactoring.md new file mode 100644 index 0000000..f15298a --- /dev/null +++ b/.gitlab/issue_templates/Refactoring.md @@ -0,0 +1,26 @@ +## Summary + + + +## Improvements + + + +## Risks + + + +## Involved components + + + +## Links diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c13f991 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..822a2ff --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Flutter app done for a uni project. Names redacted for privacy reasons. diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..29a3a50 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,43 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/app/.metadata b/app/.metadata new file mode 100644 index 0000000..6eb54a1 --- /dev/null +++ b/app/.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: "761747bfc538b5af34aa0d3fac380f1bc331ec49" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: android + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: ios + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: linux + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: macos + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: web + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + - platform: windows + create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + + # 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/app/README.md b/app/README.md new file mode 100644 index 0000000..fb76815 --- /dev/null +++ b/app/README.md @@ -0,0 +1,3 @@ +# habitrack_app + +A new Flutter project. diff --git a/app/android/.gitignore b/app/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle new file mode 100644 index 0000000..6807ba1 --- /dev/null +++ b/app/android/app/build.gradle @@ -0,0 +1,58 @@ +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" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file("local.properties") +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader("UTF-8") { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") +if (flutterVersionCode == null) { + flutterVersionCode = "1" +} + +def flutterVersionName = localProperties.getProperty("flutter.versionName") +if (flutterVersionName == null) { + flutterVersionName = "1.0" +} + +android { + namespace = "com.example.habitrack_app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.habitrack_app" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.debug + } + } +} + +flutter { + source = "../.." +} diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..c3e11fd --- /dev/null +++ b/app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..29164f1 --- /dev/null +++ b/app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/android/app/src/main/kotlin/com/example/habitrack_app/MainActivity.kt b/app/android/app/src/main/kotlin/com/example/habitrack_app/MainActivity.kt new file mode 100644 index 0000000..593bdd5 --- /dev/null +++ b/app/android/app/src/main/kotlin/com/example/habitrack_app/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.habitrack_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() diff --git a/app/android/app/src/main/res/drawable-v21/launch_background.xml b/app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/android/app/src/main/res/drawable/launch_background.xml b/app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png new file mode 100644 index 0000000..ca0b9dd Binary files /dev/null and b/app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ diff --git a/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png new file mode 100644 index 0000000..d687737 Binary files /dev/null and b/app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ diff --git a/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png new file mode 100644 index 0000000..3008af8 Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ diff --git a/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png new file mode 100644 index 0000000..9750070 Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png new file mode 100644 index 0000000..088ebee Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ diff --git a/app/android/app/src/main/res/values-night/styles.xml b/app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/android/app/src/main/res/values/styles.xml b/app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/android/build.gradle b/app/android/build.gradle new file mode 100644 index 0000000..d2ffbff --- /dev/null +++ b/app/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = "../build" +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/app/android/gradle.properties b/app/android/gradle.properties new file mode 100644 index 0000000..3b5b324 --- /dev/null +++ b/app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e1ca574 --- /dev/null +++ b/app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/app/android/settings.gradle b/app/android/settings.gradle new file mode 100644 index 0000000..536165d --- /dev/null +++ b/app/android/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/app/assets/icon/Logo.webp b/app/assets/icon/Logo.webp new file mode 100644 index 0000000..1382459 Binary files /dev/null and b/app/assets/icon/Logo.webp differ diff --git a/app/assets/icon/image.png b/app/assets/icon/image.png new file mode 100644 index 0000000..7b2b8e3 Binary files /dev/null and b/app/assets/icon/image.png differ diff --git a/app/assets/icon/logo.jpg b/app/assets/icon/logo.jpg new file mode 100644 index 0000000..5c1a9b5 Binary files /dev/null and b/app/assets/icon/logo.jpg differ diff --git a/app/assets/icon/logo.png b/app/assets/icon/logo.png new file mode 100644 index 0000000..42ee3df Binary files /dev/null and b/app/assets/icon/logo.png differ diff --git a/app/assets/water-bottle-svgrepo-com.svg b/app/assets/water-bottle-svgrepo-com.svg new file mode 100644 index 0000000..61b567c --- /dev/null +++ b/app/assets/water-bottle-svgrepo-com.svg @@ -0,0 +1,35 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/ios/.gitignore b/app/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/app/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/app/ios/Flutter/AppFrameworkInfo.plist b/app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/app/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 + 12.0 + + diff --git a/app/ios/Flutter/Debug.xcconfig b/app/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/app/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/app/ios/Flutter/Release.xcconfig b/app/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/app/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a5b8a93 --- /dev/null +++ b/app/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 = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp; + 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.habitrackApp.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.habitrackApp.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.habitrackApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp; + 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.habitrackApp; + 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/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/ios/Runner.xcworkspace/contents.xcworkspacedata b/app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/app/ios/Runner/AppDelegate.swift b/app/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..9074fee --- /dev/null +++ b/app/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/app/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/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/app/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/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/app/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/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/app/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/app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/ios/Runner/Base.lproj/Main.storyboard b/app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist new file mode 100644 index 0000000..ca621c2 --- /dev/null +++ b/app/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Habitrack App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + habitrack_app + 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/app/ios/Runner/Runner-Bridging-Header.h b/app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/app/ios/RunnerTests/RunnerTests.swift b/app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/app/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/app/l10n.yaml b/app/l10n.yaml new file mode 100644 index 0000000..4e6692e --- /dev/null +++ b/app/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/app/lib/function_widgets/compound_widgets/compound_timer_widget.dart b/app/lib/function_widgets/compound_widgets/compound_timer_widget.dart new file mode 100644 index 0000000..793c19d --- /dev/null +++ b/app/lib/function_widgets/compound_widgets/compound_timer_widget.dart @@ -0,0 +1,465 @@ +//import 'dart:ffi'; + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart'; +import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/timer.dart'; +import 'package:logger/logger.dart'; + +// ignore: must_be_immutable +class CompoundWidgetTimer extends ConsumerStatefulWidget { + CompoundWidgetTimer({required this.item, super.key}); + + TimerItem item; + + double getProgress() { + return (item.current / item.goal) / 60; + } + + @override + ConsumerState createState() => + _CompoundWidgetTimerState(); +} + +class _CompoundWidgetTimerState extends ConsumerState + with WidgetsBindingObserver { + _CompoundWidgetTimerState(); + + String button1text = ''; + Timer _timer = Timer(Duration.zero, () => ()); + + DateTime calcDue() { + final now = DateTime.now(); + const secondsToAdd = 10; + const duration = Duration(seconds: secondsToAdd); + final futureTime = now.add(duration); + + Logger().i(futureTime); + + return DateTime.now(); + } + + void handleButton1() { + //start timer from 0 + const oneSec = Duration(seconds: 1); + + if (widget.item.state == 'initial') { + widget.item = widget.item.copyWith(state: 'running'); + ref.watch(homeControllerProvider).edit(widget.item); + _timer = Timer.periodic( + oneSec, + (Timer funcTimer) { + if (widget.item.current == (widget.item.goal * 60)) { + setState(() { + final now = DateTime.now(); + widget.item = widget.item.copyWith( + state: 'completed', + completedOn: now.toString(), + ); + ref.watch(homeControllerProvider).edit(widget.item); + + funcTimer.cancel(); + }); + } else { + setState(() { + widget.item = + widget.item.copyWith(current: widget.item.current + 1); + ref.watch(homeControllerProvider).edit(widget.item); + }); + } + }, + ); + } + + // continue timer + else if (widget.item.state == 'paused') { + widget.item = widget.item.copyWith(state: 'running'); + ref.watch(homeControllerProvider).edit(widget.item); + _timer = Timer.periodic( + const Duration(seconds: 1), + (Timer funcTimer) { + if (widget.item.current == (widget.item.goal * 60)) { + setState(() { + final now = DateTime.now(); + widget.item = widget.item.copyWith( + state: 'completed', + completedOn: now.toString(), + ); + ref.watch(homeControllerProvider).edit(widget.item); + + funcTimer.cancel(); + }); + } else { + setState(() { + widget.item = + widget.item.copyWith(current: widget.item.current + 1); + ref.watch(homeControllerProvider).edit(widget.item); + }); + } + }, + ); + } else if (widget.item.state == 'running') { + widget.item = widget.item.copyWith(state: 'paused'); + ref.watch(homeControllerProvider).edit(widget.item); + + _timer.cancel(); + } else if (widget.item.state == 'completed') {} + } + + void stopTimer() { + setState(() { + if (_timer.isActive) { + _timer.cancel(); + } + widget.item = widget.item.copyWith( + current: 0, + state: 'initial', + ); + + ref.watch(homeControllerProvider).edit(widget.item); + }); + } + + String _formatCurrent() { + final hours = (widget.item.current / 3600).floor(); + final minutes = ((widget.item.current - (hours * 3600)) / 60).floor(); + return '$hours hours : $minutes minutes'; + } + + String _formattedTime() { + final minutesTotal = widget.item.goal; + + final hours = (minutesTotal / 60).floor(); + final minutes = minutesTotal - (hours * 60); + return '$hours hours : $minutes minutes'; + } + + void _toggleExpansion() { + setState(() { + widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded); + ref.watch(homeControllerProvider).edit(widget.item); + }); + } + + String _getButton1Text(BuildContext context) { + if (widget.item.state == 'initial') { + return AppLocalizations.of(context)!.timerWidget_buttonStart; + } else if (widget.item.state == 'running') { + return AppLocalizations.of(context)!.timerWidget_buttonPause; + } else if (widget.item.state == 'paused') { + return AppLocalizations.of(context)!.timerWidget_buttonContinue; + } + return 'Done'; + } + + @override + Widget build(BuildContext context) { + ref + ..watch(homeControllerProvider) + ..watch(itemsProvider); + + return Container( + margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10), + padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.circular(7)), + boxShadow: [ + BoxShadow( + color: const Color(0x00000000).withOpacity(0.25), + spreadRadius: 2, + blurRadius: 5, + // changes position of shadow + ), + ], + ), + width: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!widget.item.isExpanded) ...[ + Icon( + Icons.timer, + size: 20, + color: Theme.of(context).colorScheme.onPrimary, + ), + Expanded( + flex: 2, + child: Container( + margin: const EdgeInsets.only(left: 10, right: 10), + height: 30, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(5), + ), // Rounded corners + child: LinearProgressIndicator( + value: + widget.getProgress(), // Progress value (0.0 - 1.0) + backgroundColor: + Colors.grey.withOpacity(0.5), // Background color + valueColor: const AlwaysStoppedAnimation( + Color(0xffA4E8FD), + ), // Progress color + ), + ), + ), + ), + Expanded( + child: Text( + widget.item.name, + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, + // alignment: TextAlign.left, + ), + ), + ), + ], + if (widget.item.isExpanded) ...[ + Expanded( + child: Text( + widget.item.name, + textScaler: const TextScaler.linear(2), + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, + + // alignment: TextAlign.left, + ), + ), + ), + Expanded( + flex: 0, + child: IconButton( + icon: Icon( + Icons.settings, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: () => _showSettingsMenu(ref), + ), + ), + ], + Expanded( + flex: 0, + child: IconButton( + icon: Icon( + widget.item.isExpanded + ? Icons.arrow_drop_up_outlined + : Icons.arrow_drop_down_circle_outlined, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: _toggleExpansion, + ), + ), + ], + ), + if (widget.item.isExpanded) ...[ + // Additional child elements when expanded + SizedBox( + height: 300, + width: MediaQuery.of(context).size.width, + child: Column( + children: [ + Stack( + children: [ + Container( + alignment: Alignment.center, + // color: Colors.blueAccent, + width: MediaQuery.of(context).size.width, + height: 250, + child: SizedBox( + height: 150, + // color: Colors.greenAccent, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + // ignore: lines_longer_than_80_chars + '${AppLocalizations.of(context)!.timerWidget_current}: ${_formatCurrent()} \n ${AppLocalizations.of(context)!.timerWidget_goal}: ${_formattedTime()}', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + ], + ), + ), + ), + Positioned( + top: 10, + left: MediaQuery.of(context).size.width * 0.15, + width: MediaQuery.of(context).size.width * 0.60, + height: 220, + child: CircularProgressIndicator( + color: Theme.of(context).colorScheme.onPrimary, + strokeWidth: 7, + value: widget.getProgress(), + semanticsLabel: 'Circular progress indicator', + ), + ), + ], + ), + Expanded( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton( + onPressed: handleButton1, + child: Text( + _getButton1Text(context), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + ), + TextButton( + onPressed: stopTimer, + child: Text( + AppLocalizations.of(context)! + .timerWidget_buttonReset, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + // Add more widgets here as needed + ], + ], + ), + ); + } + + Future _showSettingsMenu(WidgetRef ref) async { + logger.i('Opening settings'); + + final settingEntries = WidgetSettingsData( + entries: { + 'name': SettingEntryText( + name: AppLocalizations.of(context)!.widgetSettings_name, + defaultValue: widget.item.name, + ), + 'duration': SettingEntryDuration( + name: AppLocalizations.of(context)!.timerWidgetSettings_duration, + defaultValue: widget.item.goal, + ), + }, + ); + + if (widget.item.state == 'running') { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.timerWidget_pausedForEdit, + ), + ), + ); + } else { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + content: WidgetSettings( + entries: settingEntries, + ), + actions: [ + OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Colors.red, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () async { + ref.watch(itemsProvider); + final items = ref.watch(homeControllerProvider); + + widget.item = widget.item.copyWith(isVisible: false); + setState(() { + items.edit(widget.item); + }); + + logger.i('Attempting delete'); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_deleteButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + fontWeight: FontWeight.bold, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + backgroundColor: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: () { + _timer.cancel(); + ref.watch(homeControllerProvider); + + logger.i('Attempting edit of name and/or duration'); + + final name = settingEntries.getValue('name') as String; + logger.i('New name: $name'); + final duration = settingEntries.getValue('duration') as int; + widget.item = + widget.item.copyWith(goal: duration, name: name); + setState(() { + ref.watch(homeControllerProvider).edit(widget.item); + }); + logger.i('NAME AND DURATION SUCCESSFULLY UPDATED'); + + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_saveButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + }, + ); + } + } +} diff --git a/app/lib/function_widgets/compound_widgets/compound_widget_tasks.dart b/app/lib/function_widgets/compound_widgets/compound_widget_tasks.dart new file mode 100644 index 0000000..a2f3917 --- /dev/null +++ b/app/lib/function_widgets/compound_widgets/compound_widget_tasks.dart @@ -0,0 +1,968 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart'; +import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/tasks_list.dart'; +import 'package:intl/intl.dart'; + +const seperator = '_SEPARATOR_'; + +// ignore: must_be_immutable +class IndividualTodoWidget extends ConsumerStatefulWidget { + IndividualTodoWidget({ + required this.todo, + required this.due, + required this.completed, + required this.item, + required this.isVisible, + required this.createdOn, + required this.completedOn, + super.key, + }); + late String todo; + late bool completed; + late DateTime due; + late DateTime createdOn; + late DateTime completedOn; + late TasksItem item; + late bool isVisible; + + @override + ConsumerState createState() => + _IndividualTodoWidgetState(); + + String stringRepr() { + return [ + todo, + due.toString().substring(0, 10), + createdOn, + completedOn, + completed, + isVisible, + ].join(seperator); + } + + void deleteItem(WidgetRef ref) { + final searchTerm = stringRepr(); + + if (completed == true) { + final completedTaskList = [...item.completedTaskList]; + //search in completed tasks list + for (var i = 0; i < completedTaskList.length; i++) { + if (searchTerm == completedTaskList.elementAt(i)) { + var newItem = searchTerm; + final toConvert = newItem.split(seperator); + final todo = toConvert.elementAtOrNull(0)!; + final due = DateTime.parse(toConvert.elementAtOrNull(1)!); + final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!); + final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!); + + final completed = + toConvert.elementAtOrNull(4)!.toLowerCase() == 'true'; + + final dateFormat = DateFormat('yyyy-MM-dd'); + final formattedDate = dateFormat.format(due); + newItem = [ + todo, + formattedDate.substring(0, 10), + createdOn, + completedOn, + completed, + false, + ].join(seperator); + + completedTaskList[i] = newItem; + final controller = ref.watch(homeControllerProvider); + item = item.copyWith(completedTaskList: completedTaskList); + controller.edit(item); + } + } + } else { + final taskList = [...item.taskList]; + for (var i = 0; i < taskList.length; i++) { + if (searchTerm == taskList.elementAt(i)) { + var newItem = searchTerm; + final toConvert = newItem.split(seperator); + final todo = toConvert.elementAtOrNull(0)!; + final due = DateTime.parse(toConvert.elementAtOrNull(1)!); + final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!); + final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!); + + final completed = + toConvert.elementAtOrNull(4)!.toLowerCase() == 'true'; + + final dateFormat = DateFormat('yyyy-MM-dd'); + final formattedDate = dateFormat.format(due); + newItem = [ + todo, + formattedDate, + createdOn, + completedOn, + completed, + false, + ].join(seperator); + + taskList[i] = newItem; + final controller = ref.watch(homeControllerProvider); + item = item.copyWith(taskList: taskList); + + controller.edit(item); + } else {} + } + } + } + + bool overdue() { + final now = DateTime.now(); + + if (DateTime(now.year, now.month, now.day).isAfter(due)) { + return true; + } + return false; + } +} + +class _IndividualTodoWidgetState extends ConsumerState { + _IndividualTodoWidgetState(); + + void _toggleChecked(bool? param) { + if (widget.overdue() && !widget.completed) { + } else { + final searchTerm = widget.stringRepr(); + + final completedTaskList = [...widget.item.completedTaskList]; + final taskList = [...widget.item.taskList]; + if (widget.completed == true) { + //search in completed tasks list + for (var i = 0; i < completedTaskList.length; i++) { + if (searchTerm == completedTaskList.elementAt(i)) { + widget.completed = !widget.completed; + final replacementStr = widget.stringRepr(); + + completedTaskList.removeAt(i); + taskList.add(replacementStr); + + final controller = ref.watch(homeControllerProvider); + widget.item = widget.item.copyWith( + taskList: taskList, + completedTaskList: completedTaskList, + ); + setState(() { + controller.edit(widget.item); + }); + } + } + } else { + for (var i = 0; i < taskList.length; i++) { + if (searchTerm == taskList.elementAt(i)) { + widget.completed = !widget.completed; + // ignore: cascade_invocations + widget.completedOn = DateTime.now(); + final replacementStr = widget.stringRepr(); + + taskList.removeAt(i); + completedTaskList.add(replacementStr); + final controller = ref.watch(homeControllerProvider); + widget.item = widget.item.copyWith( + taskList: taskList, + completedTaskList: completedTaskList, + ); + setState( + () { + controller.edit(widget.item); + }, + ); + } else {} + } + } + setState(() {}); + } + } + + void _editItem(String oldItem, String itemToAdd) { + final completedTaskList = [...widget.item.completedTaskList]; + final taskList = [...widget.item.taskList]; + if (widget.completed == true) { + //search in completed tasks list + for (var i = 0; i < completedTaskList.length; i++) { + if (oldItem == completedTaskList.elementAt(i)) { + completedTaskList.replaceRange(i, i + 1, [itemToAdd]); + final controller = ref.watch(homeControllerProvider); + widget.item = + widget.item.copyWith(completedTaskList: completedTaskList); + setState(() { + controller.edit(widget.item); + }); + } + } + } else { + for (var i = 0; i < taskList.length; i++) { + if (oldItem == taskList.elementAt(i)) { + taskList.replaceRange(i, i + 1, [itemToAdd]); + + final controller = ref.watch(homeControllerProvider); + widget.item = widget.item.copyWith(taskList: taskList); + setState( + () { + controller.edit(widget.item); + }, + ); + } else {} + } + } + } + + Future _editPopup(String oldItem) async { + final todoFieldEditController = TextEditingController(text: widget.todo); + DateTime? date = widget.due; + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + title: Text( + AppLocalizations.of(context)!.tasksWidget_editTask, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + scrollable: true, + content: Column( + children: [ + TextField( + controller: todoFieldEditController, + decoration: InputDecoration(hintText: widget.todo), + autofillHints: null, + ), + TextButton( + child: Text( + AppLocalizations.of(context)!.taskSettings_duePicker, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + onPressed: () async { + final pickedDate = await showDatePicker( + context: context, + initialDate: widget.due, + firstDate: DateTime(2024, 0, 0), + lastDate: DateTime(2101), + ); + date = pickedDate; + }, + ), + ], + ), + actions: [ + OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_cancelButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + date ??= widget.due; + if (todoFieldEditController.text.isEmpty) { + todoFieldEditController.text = widget.todo; + } + + Navigator.of(context).pop(); + final dateFormat = DateFormat('yyyy-MM-dd'); + + final formattedDate = dateFormat.format(date!); + + final newItem = [ + todoFieldEditController.text, + formattedDate, + widget.createdOn, + widget.completedOn, + widget.completed, + widget.isVisible, + ].join(seperator); + + _editItem(oldItem, newItem); + + todoFieldEditController.clear(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_saveButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + final df = DateFormat('dd.MM.yyyy'); + var dateText = + // ignore: lines_longer_than_80_chars + '${AppLocalizations.of(context)!.taskSettings_due}: ${df.format(widget.due)}'; + + var backgroundColor = Theme.of(context).colorScheme.secondary; + if (widget.overdue() && !widget.completed) { + dateText = AppLocalizations.of(context)!.tasksWidget_overdue; + backgroundColor = Theme.of(context).colorScheme.tertiary; + } + + return Container( + margin: const EdgeInsets.symmetric(vertical: 2), + padding: const EdgeInsets.all(3), + width: 300, + decoration: BoxDecoration( + border: Border.all( + width: 1.5, + ), + color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(7)), + boxShadow: [ + BoxShadow( + color: const Color(0x00000000).withOpacity(0.25), + spreadRadius: 1, + blurRadius: 2, + ), + ], + ), + child: Row( + children: [ + Expanded( + flex: 0, + child: Checkbox( + activeColor: Colors.green, + fillColor: WidgetStateProperty.resolveWith( + (states) { + if (states.contains(WidgetState.selected)) { + return null; + } + return Theme.of(context).colorScheme.onSecondary; + }, + ), + value: widget.completed, + onChanged: _toggleChecked, + ), + ), + Expanded( + flex: 2, + child: Column( + children: [ + Text( + widget.todo, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSecondary, + ), + ), + Text( + dateText, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSecondary, + ), + ), + ], + ), + ), + Expanded( + flex: 0, + child: IconButton( + onPressed: () => _editPopup(widget.stringRepr()), + icon: Icon( + Icons.edit, + color: Theme.of(context).colorScheme.onSecondary, + ), + ), + ), + Expanded( + flex: 0, + child: IconButton( + onPressed: () => setState(() { + widget.deleteItem(ref); + }), + icon: Icon( + Icons.delete, + color: Theme.of(context).colorScheme.onSecondary, + ), + ), + ), + ], + ), + ); + } +} + +// ignore: must_be_immutable +class CompoundWidgetTasks extends ConsumerStatefulWidget { + CompoundWidgetTasks({required this.item, super.key}); + + TasksItem item; + + double getProgress() { + if (item.completedTaskList.isNotEmpty) { + return item.completedTaskList.length / + (item.taskList.length + item.completedTaskList.length); + } + return 0; + } + + @override + ConsumerState createState() => + _CompoundWidgetTasksState(); +} + +class _CompoundWidgetTasksState extends ConsumerState { + final TextEditingController _todoFieldController = TextEditingController(); + + void _toggleExpansion() { + setState(() { + widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded); + ref.watch(homeControllerProvider).edit(widget.item); + }); + } + + IndividualTodoWidget _todoFromString(String input) { + final toConvert = input.split(seperator); + final todo = toConvert.elementAtOrNull(0)!; + final due = DateTime.parse(toConvert.elementAtOrNull(1)!); + final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!); + final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!); + + final completed = toConvert.elementAtOrNull(4)!.toLowerCase() == 'true'; + + final isVisible = toConvert.elementAt(5).toLowerCase() == 'true'; + + final newItem = IndividualTodoWidget( + todo: todo, + due: due, + completed: completed, + completedOn: completedOn, + item: widget.item, + isVisible: isVisible, + createdOn: createdOn, + ); + return newItem; + } + + List _buildTodoList() { + final items = []; + + final taskList = [...widget.item.taskList]; + final completedTaskList = [...widget.item.completedTaskList]; + taskList.sort( + (a, b) => + DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo( + DateTime.parse(b.split(seperator).elementAtOrNull(1)!), + ), + ); + completedTaskList.sort( + (a, b) => + DateTime.parse(a.split(seperator).elementAtOrNull(1)!).compareTo( + DateTime.parse(b.split(seperator).elementAtOrNull(1)!), + ), + ); + for (var i = 0; i < taskList.length; i++) { + final todoAsString = taskList.elementAt(i); + + final newItem = _todoFromString(todoAsString); + if (newItem.isVisible) { + items.add(newItem); + } + } + for (var i = 0; i < completedTaskList.length; i++) { + final todoAsString = completedTaskList.elementAt(i); + final newItem = _todoFromString(todoAsString); + if (newItem.isVisible) { + items.add(newItem); + } + } + + return items; + } + + void _addItem(String toAdd) { + logger.i('ITEM TO ADD: $toAdd'); + final controller = ref.watch(homeControllerProvider); + + final toCopy = [...widget.item.taskList]; + // ignore: cascade_invocations + toCopy.add(toAdd); + widget.item = widget.item.copyWith(taskList: toCopy); + setState(() { + controller.edit(widget.item); + }); + } + + @override + Widget build(BuildContext context) { + ref.watch(itemsProvider); + + return Container( + margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10), + padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15), + //height: 100, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.circular(7)), + boxShadow: [ + BoxShadow( + color: const Color(0x00000000).withOpacity(0.25), + spreadRadius: 2, + blurRadius: 5, + // changes position of shadow + ), + ], + ), + width: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!widget.item.isExpanded) ...[ + Icon( + Icons.task, + size: 20, + color: Theme.of(context).colorScheme.onPrimary, + ), + Expanded( + flex: 2, + child: Container( + margin: const EdgeInsets.only(left: 10, right: 10), + // Width of the progress bar + height: 30, // Height of the progress bar + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(5), + ), // Rounded corners + child: LinearProgressIndicator( + value: + widget.getProgress(), // Progress value (0.0 - 1.0) + backgroundColor: + Colors.grey.withOpacity(0.5), // Background color + valueColor: const AlwaysStoppedAnimation( + Color(0xffA4E8FD), + ), // Progress color + ), + ), + ), + ), + Expanded( + child: Text( + widget.item.name, + style: TextStyle( + // fontSize: 12, + // fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + if (widget.item.isExpanded) ...[ + Expanded( + child: Text( + widget.item.name, + textScaler: const TextScaler.linear(2), + style: TextStyle( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + Expanded( + flex: 0, + child: IconButton( + icon: Icon( + Icons.settings, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: () => _showSettingsMenu(ref, context), + ), + ), + ], + Expanded( + flex: 0, + child: IconButton( + icon: Icon( + widget.item.isExpanded + ? Icons.arrow_drop_up_outlined + : Icons.arrow_drop_down_circle_outlined, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: _toggleExpansion, + ), + //child: Icon(Icons.arrow_drop_down_circle_outlined), + ), + ], + ), + if (widget.item.isExpanded) ...[ + // Additional child elements when expanded + SizedBox( + width: MediaQuery.of(context).size.width, + child: Column( + children: [ + Column( + children: [ + OutlinedButton( + onPressed: _addPopup, + child: Row( + children: [ + Icon( + Icons.add_task_outlined, + color: Theme.of(context).colorScheme.onPrimary, + ), + Text( + // ignore: lines_longer_than_80_chars + ' ${AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel}', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ListView( + padding: const EdgeInsets.symmetric(vertical: 5), + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + children: _buildTodoList(), + ), + if (anyTasksCompleted()) ...[ + OutlinedButton( + onPressed: _deleteCompletedTasks, + child: Text( + AppLocalizations.of(context)! + .tasksWidget_deleteDone, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + /* if (anyTasksOverdue()) ...[ + OutlinedButton( + onPressed: _rescheduleOverdueTasks, + child: Text( + AppLocalizations.of(context)! + .tasksWidget_rescheduleOverdue, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: + Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ],*/ + ], + ), + ], + ), + ), + ], + ], + ), + ); + } + + bool anyTasksCompleted() { + for (final item in widget.item.completedTaskList) { + final toConvert = item.split(seperator); + + final isVisible = toConvert.elementAt(5).toLowerCase() == 'true'; + + if (isVisible) { + return true; + } + } + return false; + } + + void _deleteCompletedTasks() { + final controller = ref.watch(homeControllerProvider); + + final newCompletedTaskList = []; // widget.item.completedTaskList; + for (final itemString in widget.item.completedTaskList) { + final oldItem = _todoFromString(itemString); + + final dateFormat = DateFormat('yyyy-MM-dd'); + + final newItem = [ + oldItem.todo, + dateFormat.format(oldItem.due), + oldItem.createdOn, + oldItem.completedOn, + oldItem.completed, + false, + ].join(seperator); + newCompletedTaskList.add(newItem); + } + widget.item = widget.item.copyWith(completedTaskList: newCompletedTaskList); + controller.edit(widget.item); + } + + bool anyTasksOverdue() { + final taskList = [...widget.item.taskList]; + for (final taskString in taskList) { + if (_todoFromString(taskString).overdue()) { + return true; + } + } + return false; + } + + // Used for the Reschedule Task Button + // ignore: unused_element + void _rescheduleOverdueTasks() { + final taskList = [...widget.item.taskList]; + + for (var i = 0; i < taskList.length; i++) { + final taskString = taskList[i]; + if (_todoFromString(taskString).overdue()) { + final oldItem = _todoFromString(taskString); + final now = DateTime.now(); + final newItem = IndividualTodoWidget( + todo: oldItem.todo, + due: DateTime(now.year, now.month, now.day), + createdOn: DateTime.now(), + completedOn: DateTime.now(), + completed: oldItem.completed, + item: widget.item, + isVisible: true, + ); + taskList.replaceRange(i, i + 1, [newItem.stringRepr()]); + + final controller = ref.watch(homeControllerProvider); + widget.item = widget.item.copyWith(taskList: taskList); + setState( + () { + controller.edit(widget.item); + }, + ); + } + } + } + + Future _addPopup() async { + DateTime? date = DateTime.now(); + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + title: Text( + AppLocalizations.of(context)!.tasksWidget_addTaskButtonLabel, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + fontSize: 20, + ), + ), + scrollable: true, + content: Column( + children: [ + TextField( + controller: _todoFieldController, + autofillHints: null, + decoration: InputDecoration( + hintText: AppLocalizations.of(context)!.taskSettings_name, + ), + ), + TextButton( + child: Text( + AppLocalizations.of(context)!.taskSettings_duePicker, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + onPressed: () async { + final pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), //get today's date + firstDate: DateTime.now(), + //DateTime.now() - not to allow to choose before today + lastDate: DateTime(2101), + ); + date = pickedDate; + }, + ), + ], + ), + actions: [ + OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_cancelButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + //DateTime due = DateTime.parse(formattedDate); + // ignore: cascade_invocations + + if (_todoFieldController.text.isEmpty) { + _todoFieldController.text = 'todo'; + } + + if (date != null && _todoFieldController.text.isNotEmpty) { + final dateFormat = DateFormat('yyyy-MM-dd'); + final formattedDate = dateFormat.format(date!); + Navigator.of(context).pop(); + + final itemToAdd = [ + _todoFieldController.text, + formattedDate, + DateTime.now(), + DateTime.now(), + false, + true, + ].join(seperator); + + _addItem(itemToAdd); + _todoFieldController.clear(); + } + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_saveButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + ); + }, + ); + } + + Future _showSettingsMenu(WidgetRef ref, BuildContext context) async { + final settingEntries = WidgetSettingsData( + entries: { + 'name': SettingEntryText( + name: AppLocalizations.of(context)!.widgetSettings_name, + defaultValue: widget.item.name, + ), + }, + ); + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + content: WidgetSettings( + entries: settingEntries, + ), + actions: [ + OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Colors.red, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + ref + .watch(homeControllerProvider) + .edit(widget.item.copyWith(isVisible: false)); + // await controller.delete(widget.item.id); + + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_deleteButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + fontWeight: FontWeight.bold, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + final controller = ref.watch(homeControllerProvider); + + final name = settingEntries.getValue('name') as String; + + widget.item = widget.item.copyWith(name: name); + setState(() { + widget.item = widget.item.copyWith(name: name); + + controller.edit(widget.item); + }); + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_saveButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + }, + ); + } +} diff --git a/app/lib/function_widgets/compound_widgets/compound_widget_water.dart b/app/lib/function_widgets/compound_widgets/compound_widget_water.dart new file mode 100644 index 0000000..5d5e1dc --- /dev/null +++ b/app/lib/function_widgets/compound_widgets/compound_widget_water.dart @@ -0,0 +1,491 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart'; +import 'package:habitrack_app/function_widgets/widget_settings_menu/widget_settings.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/hydration.dart'; +import 'package:liquid_progress_indicator_v2/liquid_progress_indicator.dart'; + +// ignore: must_be_immutable +class CompoundWidgetWater extends ConsumerStatefulWidget { + CompoundWidgetWater({required this.item, super.key}); + + late Hydration item; + + double getProgress() { + return item.current / item.goal; + } + + @override + ConsumerState createState() => + _CompoundWidgetWaterState(); +} + +class _CompoundWidgetWaterState extends ConsumerState { + void _toggleExpansion() { + setState(() { + widget.item = widget.item.copyWith(isExpanded: !widget.item.isExpanded); + ref.watch(homeControllerProvider).edit(widget.item); + }); + } + + void _addQuantity(int toAdd) { + setState(() { + final controller = ref.watch(homeControllerProvider); + final oldval = widget.item.current; + + logger.i('Old item: ${widget.item}'); + + widget.item = widget.item.copyWith(current: oldval + toAdd); + logger.i('New item: ${widget.item}'); + + controller.edit(widget.item); + }); + } + + @override + Widget build(BuildContext context) { + ref.watch(itemsProvider); + + return Container( + margin: const EdgeInsets.only(left: 10, top: 10, right: 10, bottom: 10), + padding: const EdgeInsets.only(left: 10, top: 15, right: 10, bottom: 15), + //height: 100, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.all(Radius.circular(7)), + boxShadow: [ + BoxShadow( + color: const Color(0x00000000).withOpacity(0.25), + spreadRadius: 2, + blurRadius: 5, + // changes position of shadow + ), + ], + ), + width: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!widget.item.isExpanded) ...[ + Icon( + Icons.local_drink, + size: 20, + color: Theme.of(context).colorScheme.onPrimary, + ), + Expanded( + flex: 2, + child: Container( + margin: const EdgeInsets.only(left: 10, right: 10), + // Width of the progress bar + height: 30, // Height of the progress bar + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular(5), + ), // Rounded corners + child: LinearProgressIndicator( + value: + widget.getProgress(), // Progress value (0.0 - 1.0) + backgroundColor: + Colors.grey.withOpacity(0.5), // Background color + valueColor: const AlwaysStoppedAnimation( + Color(0xffA4E8FD), + ), // Progress color + ), + ), + ), + ), + Expanded( + child: Text( + widget.item.name, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + if (widget.item.isExpanded) ...[ + Expanded( + child: Text( + widget.item.name, + textScaler: const TextScaler.linear(2), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + Expanded( + flex: 0, + child: IconButton( + icon: Icon( + Icons.settings, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: () => _showSettingsMenu(ref), + ), + //child: Icon(Icons.arrow_drop_down_circle_outlined), + ), + ], + Expanded( + flex: 0, + child: IconButton( + icon: Icon( + widget.item.isExpanded + ? Icons.arrow_drop_up_outlined + : Icons.arrow_drop_down_circle_outlined, + color: Theme.of(context).colorScheme.onPrimary, + ), + onPressed: _toggleExpansion, + ), + //child: Icon(Icons.arrow_drop_down_circle_outlined), + ), + ], + ), + if (widget.item.isExpanded) ...[ + // Additional child elements when expanded + SizedBox( + height: 300, + width: MediaQuery.of(context).size.width, + child: Column( + children: [ + //INSERT WIDGET SPECIFIC STUFF HERE + SizedBox( + height: 300, + width: MediaQuery.of(context).size.width, + child: Row( + children: [ + Expanded( + child: OvershootLiquidLinearProgressIndicator( + current: widget.item.current, + goal: widget.item.goal, + ), + ), + Expanded( + child: Column( + children: [ + Text( + '${widget.item.current / 1000} / ${widget.item.goal / 1000} L', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + Expanded( + child: OutlinedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.secondary, + ), + ), + onPressed: () => { + _addQuantity( + widget.item.button1Amount, + ), + }, + child: Text( + '${widget.item.button1Amount} mL', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .onSecondary, + ), + ), + ), + ), + Expanded( + child: OutlinedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.secondary, + ), + ), + onPressed: () => { + _addQuantity( + widget.item.button2Amount, + ), + }, + child: Text( + '${widget.item.button2Amount} mL', + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + ), + ), + Expanded( + child: OutlinedButton( + style: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + Theme.of(context).colorScheme.secondary, + ), + ), + onPressed: _showPopupCustomAmount, + child: Text( + AppLocalizations.of(context)! + .waterWidget_customAmountButton, + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .onPrimary, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + // Add more widgets here as needed + ], + ], + ), + ); + } + + Future _showSettingsMenu(WidgetRef ref) async { + final settingEntries = WidgetSettingsData( + entries: { + 'name': SettingEntryText( + name: AppLocalizations.of(context)!.widgetSettings_name, + defaultValue: widget.item.name, + ), + 'button1Amount': SettingEntryNumeric( + name: AppLocalizations.of(context)!.waterWidgetSettings_button1, + defaultValue: widget.item.button1Amount, + ), + 'button2Amount': SettingEntryNumeric( + name: AppLocalizations.of(context)!.waterWidgetSettings_button2, + defaultValue: widget.item.button2Amount, + ), + 'targetGoal': SettingEntrySlider( + name: AppLocalizations.of(context)!.waterWidgetSettings_goal, + defaultValue: double.parse(widget.item.goal.toString()), + divisions: 80, + topValue: 4000, + ), + 'currentAmount': SettingEntryNumeric( + name: AppLocalizations.of(context)!.waterWidgetSettings_current, + defaultValue: widget.item.current, + ), + }, + ); + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + content: WidgetSettings( + entries: settingEntries, + ), + actions: [ + OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Colors.red, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + //widget.parent.delete(ref); + ref.watch(itemsProvider); + final controller = ref.watch(homeControllerProvider); + + widget.item = widget.item.copyWith(isVisible: false); + controller.edit(widget.item); + + logger.i('Attempting delete'); + // ignore: use_build_context_synchronously + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_deleteButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + fontWeight: FontWeight.bold, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.onPrimary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + final controller = ref.watch(homeControllerProvider); + + logger.i('Attempting edit of water widget stuff'); + // widget.settingEntries.notify(); + final name = settingEntries.getValue('name') as String; + final currentAmount = + settingEntries.getValue('currentAmount') as int; + final targetGoal = + (settingEntries.getValue('targetGoal') as double).round(); + final button1Amount = + settingEntries.getValue('button1Amount') as int; + final button2Amount = + settingEntries.getValue('button2Amount') as int; + + widget.item = widget.item.copyWith( + name: name, + current: currentAmount, + goal: targetGoal, + button1Amount: button1Amount, + button2Amount: button2Amount, + ); + controller.edit(widget.item); + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_saveButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + }, + ); + } + + Future _showPopupCustomAmount() async { + final customFieldController = TextEditingController(); + + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + scrollable: true, + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + title: Text( + AppLocalizations.of(context)!.waterWidget_customAmountMessage, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ), + content: Column( + children: [ + TextField( + controller: customFieldController, + keyboardType: TextInputType.number, + autofocus: true, + ), + ], + ), + actions: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + backgroundColor: Theme.of(context).colorScheme.primary, + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_cancelButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + final result = int.tryParse(customFieldController.text) ?? 0; + _addQuantity(result); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_saveButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + ); + }, + ); + } +} + +class OvershootLiquidLinearProgressIndicator extends StatelessWidget { + const OvershootLiquidLinearProgressIndicator({ + required this.current, + required this.goal, + super.key, + }); + + final int current; + final int goal; + + @override + Widget build(BuildContext context) { + var mainColor = Colors.blue; + var backColor = Colors.white; + var value = 0.0; + if (current < goal) { + mainColor = Colors.blue; + backColor = Colors.white; + value = (current - goal * 0) / goal; + } else if (current < goal * 2) { + mainColor = Colors.orange; + backColor = Colors.blue; + value = (current - goal * 1) / goal; + } else { + mainColor = Colors.red; + backColor = Colors.orange; + value = (current - goal * 2) / goal; + } + return LiquidLinearProgressIndicator( + value: value, + valueColor: AlwaysStoppedAnimation( + mainColor, + ), + backgroundColor: backColor, + borderColor: Colors.black, + borderWidth: 3, + borderRadius: 12, + direction: Axis.vertical, + ); + } +} diff --git a/app/lib/function_widgets/widget_settings_menu/setting_entry.dart b/app/lib/function_widgets/widget_settings_menu/setting_entry.dart new file mode 100644 index 0000000..6d59356 --- /dev/null +++ b/app/lib/function_widgets/widget_settings_menu/setting_entry.dart @@ -0,0 +1,375 @@ +import 'package:duration_picker/duration_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:intl/intl.dart'; + +abstract class SettingEntry extends StatefulWidget { + const SettingEntry({required this.name, super.key}); + + final String name; + + @override + State createState() => _SettingEntryState(); + + dynamic getValue() { + throw UnimplementedError(); + } + + void setValue(dynamic newValue) { + throw UnimplementedError(); + } +} + +class _SettingEntryState extends State { + @override + Widget build(BuildContext context) { + return const Text('Abstract, not implemented'); + } +} + +// ###############################TEXT######################################### +class SettingEntryText extends SettingEntry { + SettingEntryText({ + required super.name, + this.defaultValue = 'Some Text', + super.key, + }) { + setValue(defaultValue); + } + + final TextEditingController valueController = TextEditingController(); + final String defaultValue; + + @override + State createState() => _SettingEntryTextState(); + + @override + String getValue() { + logger.i('GETTING VALUE ${valueController.text}'); + return valueController.text; + } + + @override + void setValue(dynamic newValue) { + if (newValue is! String) { + throw Exception('Value of SettingEntryText can only be a String!'); + } + valueController.text = newValue; + } +} + +class _SettingEntryTextState extends State { + void monitorTextChange() { + final text = widget.valueController.text; + logger.i('TEXT: $text'); + } + + @override + void initState() { + super.initState(); + + // Start listening to changes. + widget.valueController.addListener(monitorTextChange); + } + + @override + void dispose() { + // Clean up the controller when the widget is removed from the widget tree. + // This also removes the _printLatestValue listener. + widget.valueController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + alignment: AlignmentDirectional.bottomStart, + margin: const EdgeInsets.only(top: 7.5, bottom: 7.5), + + // color: Colors.black, + child: Text( + widget.name, + textAlign: TextAlign.left, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + ), + TextField( + keyboardType: TextInputType.text, + controller: widget.valueController, + ), + ], + ); + } +} + +// ###############################NUMERIC####################################### +class SettingEntryNumeric extends SettingEntry { + SettingEntryNumeric({required super.name, int defaultValue = 0, super.key}) { + setValue(defaultValue); + } + + final TextEditingController valueController = TextEditingController(); + + @override + State createState() => _SettingEntryNumericState(); + + @override + int getValue() { + return int.parse(valueController.text); + } + + @override + void setValue(dynamic newValue) { + if (newValue is! int) { + throw Exception('Value of SettingEntryNumeric can only be an integer!'); + } + valueController.text = newValue.toString(); + } +} + +class _SettingEntryNumericState extends State { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text(widget.name), + TextField( + keyboardType: TextInputType.number, + controller: widget.valueController, + ), + ], + ); + } +} + +// ###############################SLIDER######################################## +class SettingEntrySlider extends SettingEntry { + SettingEntrySlider({ + required this.topValue, + required this.divisions, + required super.name, + double defaultValue = 0.0, + super.key, + }) { + setValue(defaultValue); + } + + final double topValue; + final int divisions; + final value = DoubleSaver(); + + @override + State createState() => _SettingEntrySliderState(); + + @override + double getValue() { + return value.v; + } + + @override + void setValue(dynamic newValue) { + if (newValue is! double) { + throw Exception('Value of SettingEntrySlider can only be a Double!'); + } + value.v = newValue; + } +} + +class DoubleSaver { + double v = 0; +} + +class _SettingEntrySliderState extends State { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + //Expanded(flex: 2, child: Text(widget.name)), + Text(widget.name), + Text(' ${widget.getValue()}'), + ], + ), + Row( + children: [ + const Text('0 '), + Expanded( + child: Slider( + value: widget.getValue(), + divisions: widget.divisions, + max: widget.topValue, + onChanged: sliderChange, + ), + ), + Text(' ${widget.topValue}'), + ], + ), + ], + ); + } + + void sliderChange(double newValue) { + setState(() { + widget.setValue(newValue); + }); + } +} + +// #############################DURATION####################################### + +class IntSaver { + IntSaver({this.v = 30}); + int v; +} + +class SettingEntryDuration extends SettingEntry { + SettingEntryDuration({ + required super.name, + required this.defaultValue, + super.key, + }) { + val = IntSaver(v: defaultValue); + } + final int defaultValue; + late final IntSaver val; + + @override + State createState() => _SettingEntryDurationState(); + + @override + int getValue() { + return val.v; + } + + @override + void setValue(dynamic newValue) { + if (newValue is! int) { + throw Exception('Value of SettingEntryDuration can only be a Double!'); + } + val.v = newValue; + } +} + +class _SettingEntryDurationState extends State { + @override + Widget build(BuildContext context) { + final textCurrent = + AppLocalizations.of(context)!.widgetSettings_durationPickerCurrent; + return Column( + children: [ + Container( + alignment: AlignmentDirectional.bottomStart, + margin: const EdgeInsets.only(top: 7.5, bottom: 7.5), + child: Text( + '$textCurrent ${widget.getValue()}m', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + ), + OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + ), + onPressed: () async { + var resultingDuration = await showDurationPicker( + context: context, + initialTime: Duration(minutes: widget.val.v), + ); + resultingDuration ??= Duration(minutes: widget.defaultValue); + // ignore: use_build_context_synchronously + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Chose duration: ${resultingDuration.inMinutes}'), + ), + ); + widget.setValue(resultingDuration.inMinutes); + setState(() {}); + }, + child: Text( + AppLocalizations.of(context)!.widgetSettings_durationPickerButton, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + ); + } +} + +// ###############################DATE######################################### +class DateSaver { + DateTime v = DateTime.now(); +} + +class SettingEntryDate extends SettingEntry { + SettingEntryDate({required super.name, DateTime? defaultValue, super.key}) { + this.defaultValue = defaultValue ?? DateTime.now(); + setValue(defaultValue); + } + + final date = DateSaver(); + + late final DateTime defaultValue; + + @override + State createState() => _SettingEntryDateState(); + + @override + DateTime getValue() { + return date.v; + } + + @override + void setValue(dynamic newValue) { + if (newValue is! DateTime) { + throw Exception('Value of SettingEntryNumeric can only be a DateTime!'); + } + date.v = newValue; + } +} + +class _SettingEntryDateState extends State { + @override + Widget build(BuildContext context) { + final dateFormat = DateFormat('dd. MMMM'); + return Column( + children: [ + Text( + '${widget.name}: ${dateFormat.format(widget.date.v)} ', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.primaryContainer, + ), + ), + TextButton( + onPressed: datePicker, + child: const Text('Choose a date'), + ), + ], + ); + } + + Future datePicker() async { + final pickedDate = await showDatePicker( + context: context, + initialDate: widget.date.v, //get today's date + firstDate: DateTime.now(), + //DateTime.now() - not to allow to choose before today. + lastDate: DateTime(2101), + ); + final cleanDate = pickedDate ?? widget.date.v; + widget.setValue(cleanDate); + setState(() {}); + } +} diff --git a/app/lib/function_widgets/widget_settings_menu/widget_settings.dart b/app/lib/function_widgets/widget_settings_menu/widget_settings.dart new file mode 100644 index 0000000..ab8bea4 --- /dev/null +++ b/app/lib/function_widgets/widget_settings_menu/widget_settings.dart @@ -0,0 +1,79 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:habitrack_app/function_widgets/widget_settings_menu/setting_entry.dart'; + +class WidgetSettingsData { + WidgetSettingsData({required this.entries}); + + final Map listeners = {}; + final Map entries; + + void addListener(State toAdd, void Function(void Function()) updateCall) { + listeners[toAdd] = updateCall; + } + + void removeListener(State toRemove) { + listeners.remove(toRemove); + } + + void notify() { + for (final listener in listeners.keys) { + listeners[listener]!(() => ()); + } + } + + dynamic getValue(String key) { + return entries[key]?.getValue(); + } + + void setValue(String key, dynamic value) { + entries[key]?.setValue(value); + notify(); + } + + List asList() { + return entries.values.toList(); + } +} + +class WidgetSettings extends StatefulWidget { + const WidgetSettings({ + required this.entries, + super.key, + }); + + final WidgetSettingsData entries; + + @override + State createState() => _WidgetSettingsState(); +} + +class _WidgetSettingsState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + iconTheme: IconThemeData( + color: Theme.of(context).colorScheme.onPrimaryContainer,), + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + title: Text( + AppLocalizations.of(context)!.settingsHeader, + textScaler: const TextScaler.linear(1.5), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + ), + body: ColoredBox( + color: Theme.of(context).colorScheme.primaryContainer, + child: ListView( + children: widget.entries.asList(), + ), + ), + ); + } +} diff --git a/app/lib/infrastructure/bottom_navigation.dart b/app/lib/infrastructure/bottom_navigation.dart new file mode 100644 index 0000000..c88ebce --- /dev/null +++ b/app/lib/infrastructure/bottom_navigation.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +import 'package:go_router/go_router.dart'; + +class ScaffoldWithBottomNavigationBar extends StatelessWidget { + const ScaffoldWithBottomNavigationBar({ + required this.navigationShell, + Key? key, + }) : super(key: key ?? const ValueKey('ScaffoldWithNavBar')); + + final StatefulNavigationShell navigationShell; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SafeArea(child: navigationShell), + bottomNavigationBar: BottomNavigationBar( + fixedColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: Theme.of(context).colorScheme.secondary, + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.list), + label: 'Widget Wall', + ), + BottomNavigationBarItem( + icon: Icon(Icons.dashboard), + label: 'Dashboard', + ), + ], + currentIndex: navigationShell.currentIndex, + onTap: (int index) => _onTap(context, index), + ), + ); + } + + void _onTap(BuildContext context, int index) { + navigationShell.goBranch( + index, + initialLocation: index == navigationShell.currentIndex, + ); + } +} diff --git a/app/lib/infrastructure/routing.dart b/app/lib/infrastructure/routing.dart new file mode 100644 index 0000000..e75a036 --- /dev/null +++ b/app/lib/infrastructure/routing.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import 'package:habitrack_app/infrastructure/bottom_navigation.dart'; +import 'package:habitrack_app/pages/dashboard_page.dart'; +import 'package:habitrack_app/pages/widget_page.dart'; + +// navigators, root and each destination of bottom navigation bar +final _rootNavigatorKey = GlobalKey(); + +final _shellNavigatorWidgetWallKey = + GlobalKey(debugLabel: 'widgetWall'); + +final _shellNavigatorDashboardKey = + GlobalKey(debugLabel: 'dashboard'); + +const String _widgetWallPath = '/widgetWall'; +const String _dashboardPath = '/dashboard'; + +final goRouter = GoRouter( + initialLocation: _widgetWallPath, + navigatorKey: _rootNavigatorKey, + debugLogDiagnostics: true, + routes: [ + StatefulShellRoute.indexedStack( + builder: (context, state, navigationShell) { + return ScaffoldWithBottomNavigationBar( + navigationShell: navigationShell, + ); + }, + branches: [ + StatefulShellBranch( + navigatorKey: _shellNavigatorWidgetWallKey, + routes: [ + GoRoute( + path: _widgetWallPath, + pageBuilder: (context, state) => const NoTransitionPage( + child: WidgetPage(), + ), + ), + ], + ), + StatefulShellBranch( + navigatorKey: _shellNavigatorDashboardKey, + routes: [ + GoRoute( + path: _dashboardPath, + pageBuilder: (context, state) => const NoTransitionPage( + child: DashboardPage(), + ), + ), + ], + ), + ], + ), + ], +); diff --git a/app/lib/infrastructure/widget_wall/add_widget_button.dart b/app/lib/infrastructure/widget_wall/add_widget_button.dart new file mode 100644 index 0000000..de68129 --- /dev/null +++ b/app/lib/infrastructure/widget_wall/add_widget_button.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/add_widget_menu.dart'; + +class AddWidgetButton extends StatelessWidget { + const AddWidgetButton({super.key}); + + @override + Widget build(BuildContext context) { + return OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + ), + onPressed: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const AddWidgetMenu(), + ), + ), + }, + child: Icon( + Icons.add, + color: Theme.of(context).colorScheme.onPrimary, + ), + ); + } +} diff --git a/app/lib/infrastructure/widget_wall/add_widget_menu.dart b/app/lib/infrastructure/widget_wall/add_widget_menu.dart new file mode 100644 index 0000000..63c4856 --- /dev/null +++ b/app/lib/infrastructure/widget_wall/add_widget_menu.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/hydration.dart'; +import 'package:habitrack_app/sembast/tasks_list.dart'; +import 'package:habitrack_app/sembast/timer.dart'; + +//Add Widget to List- Button Class ###################################### +class _AddWidgetToList extends ConsumerWidget { + const _AddWidgetToList({ + required this.toAdd, + required this.buttonText, + required this.iconData, + }); + + final dynamic toAdd; + final String buttonText; + final IconData iconData; + + void _buttonFunc(BuildContext context, WidgetRef ref) { + //ref.read(widgetListNotifierProvider.notifier).addWidget(toAdd()); + //ref.read(homeControllerProvider).add() + ref.watch(homeControllerProvider).add(toAdd); + + Navigator.pop(context); + logger.i('Button Func Called'); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.symmetric(vertical: 4), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + boxShadow: [ + BoxShadow( + color: const Color(0x00000000).withOpacity(0.25), + spreadRadius: 1, + blurRadius: 2, + ), + ], + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + width: 300, + child: TextButton( + onPressed: () => {_buttonFunc(context, ref)}, + child: Row( + children: [ + Icon( + iconData, + color: Theme.of(context).colorScheme.onPrimary, + ), + Text( + buttonText, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ); + } +} + +// AddWidgetMenu ########################################################## +class AddWidgetMenu extends StatelessWidget { + const AddWidgetMenu({super.key}); + + @override + Widget build(BuildContext context) { + //AddWidgetToList - Button-Instances ####################################### + final addWaterWidget = _AddWidgetToList( + toAdd: Hydration( + createdOn: DateTime.now().toString(), + completedOn: '', + isVisible: true, + widgetType: 'Hydration', + name: AppLocalizations.of(context)!.waterWidget_defaultName, + button1Amount: 100, + button2Amount: 250, + goal: 2500, + current: 0, + isExpanded: false, + ), + buttonText: AppLocalizations.of(context)!.addWidget_water, + iconData: Icons.local_drink, + ); + + final addCompoundTimerWidget = _AddWidgetToList( + toAdd: TimerItem( + widgetType: 'Timer', + name: AppLocalizations.of(context)!.timerWidget_defaultName, + current: 0, + goal: 90, + isExpanded: false, + createdOn: DateTime.now().toString(), + completedOn: '', + isVisible: true, + state: 'initial', + ), + buttonText: AppLocalizations.of(context)!.addWidget_timer, + iconData: Icons.timer, + ); + final addTaskWidget = _AddWidgetToList( + toAdd: TasksItem( + isVisible: true, + widgetType: 'TODO', + name: AppLocalizations.of(context)!.tasksWidget_defaultName, + isExpanded: false, + taskList: [], + completedTaskList: [], + ), + buttonText: AppLocalizations.of(context)!.addWidget_tasks, + iconData: Icons.task, + ); + + return Scaffold( + appBar: AppBar( + iconTheme: IconThemeData( + color: Theme.of(context).colorScheme.onPrimary, + ), + title: Text( + AppLocalizations.of(context)!.addWidgetHeader, + textScaler: const TextScaler.linear(1.5), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + foregroundColor: Theme.of(context).colorScheme.primary, + ), + body: Container( + color: Theme.of(context).colorScheme.primaryContainer, + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + addWaterWidget, + addTaskWidget, + addCompoundTimerWidget, + ], + ), + ), + ); + } +} diff --git a/app/lib/infrastructure/widget_wall/graph_widget.dart b/app/lib/infrastructure/widget_wall/graph_widget.dart new file mode 100644 index 0000000..73e474c --- /dev/null +++ b/app/lib/infrastructure/widget_wall/graph_widget.dart @@ -0,0 +1,109 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; + +class DataPoint { + DataPoint({required this.date, required this.progress}); + DateTime date; + double progress; +} + +class GraphWidget extends ConsumerStatefulWidget { + GraphWidget({super.key}); + + @override + ConsumerState createState() => _GraphWidgetState(); + + final entries = [ + DataPoint(date: DateTime(2024, 7, 4), progress: 1), + DataPoint(date: DateTime(2024, 7, 5), progress: 0.5), + DataPoint(date: DateTime(2024, 7, 6), progress: 1.25), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + ]; +} + +class _GraphWidgetState extends ConsumerState { + @override + Widget build(BuildContext context) { + final firstDate = widget.entries[0].date; + final flSpots = []; + for (final dataPoint in widget.entries) { + final xValue = _daysBetween(firstDate, dataPoint.date).toDouble(); + final lcbd = FlSpot(xValue, dataPoint.progress); + flSpots.add(lcbd); + } + final df = DateFormat('dd. MMMM'); + + return Column( + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.85, + height: MediaQuery.of(context).size.height * 0.5, + child: LineChart( + LineChartData( + minY: 0, + maxY: 1.5, + lineBarsData: [ + LineChartBarData(spots: flSpots, isCurved: true), + ], + gridData: const FlGridData(show: false), + extraLinesData: ExtraLinesData( + horizontalLines: [ + HorizontalLine( + y: 1, + color: Colors.red, + ), + ], + ), + titlesData: FlTitlesData( + rightTitles: const AxisTitles( + sideTitles: SideTitles( + reservedSize: 30, + interval: 20, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles( + interval: 20, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + getTitlesWidget: (value, meta) => Text(value.toString()), + interval: 0.25, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + getTitlesWidget: (value, meta) => Text( + df.format( + DateTime( + firstDate.year, + firstDate.month, + firstDate.day, + ).add(Duration(days: value.round())), + ), + ), + showTitles: true, + interval: 1, + ), + ), + ), + ), + ), + ), + const Text('You worked for X hours this past week'), + const Text('Out of a total of Y hours planned'), + ], + ); + } + + int _daysBetween(DateTime from, DateTime to) { + final d1 = DateTime(from.year, from.month, from.day); + final d2 = DateTime(to.year, to.month, to.day); + return (d2.difference(d1).inHours / 24).round(); + } +} diff --git a/app/lib/infrastructure/widget_wall/items_controller.dart b/app/lib/infrastructure/widget_wall/items_controller.dart new file mode 100644 index 0000000..44e54ec --- /dev/null +++ b/app/lib/infrastructure/widget_wall/items_controller.dart @@ -0,0 +1,27 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/sembast/item_repository.dart'; +import 'package:habitrack_app/sembast/sembast_item_repository.dart'; + +final homeControllerProvider = Provider( + (ref) => HomeController( + itemRepository: ref.watch(itemRepositoryProvider), + ), +); + +class HomeController { + HomeController({required this.itemRepository}); + + final ItemRepository itemRepository; + + Future delete(int id) async { + await itemRepository.deleteItem(id); + } + + Future edit(dynamic item) async { + await itemRepository.updateItem(item); + } + + Future add(dynamic newItem) async { + await itemRepository.insertItem(newItem); + } +} diff --git a/app/lib/infrastructure/widget_wall/items_state.dart b/app/lib/infrastructure/widget_wall/items_state.dart new file mode 100644 index 0000000..9cec5cb --- /dev/null +++ b/app/lib/infrastructure/widget_wall/items_state.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/sembast/sembast_item_repository.dart'; + +final itemsProvider = StreamProvider( + (ref) => ref.watch(itemRepositoryProvider).getAllItemsStream(), +); diff --git a/app/lib/infrastructure/widget_wall/widget_wall.dart b/app/lib/infrastructure/widget_wall/widget_wall.dart new file mode 100644 index 0000000..e03a8a9 --- /dev/null +++ b/app/lib/infrastructure/widget_wall/widget_wall.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/function_widgets/compound_widgets/compound_timer_widget.dart'; +import 'package:habitrack_app/function_widgets/compound_widgets/compound_widget_tasks.dart'; +import 'package:habitrack_app/function_widgets/compound_widgets/compound_widget_water.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/add_widget_button.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/hydration.dart'; +import 'package:habitrack_app/sembast/tasks_list.dart'; +import 'package:habitrack_app/sembast/timer.dart'; + +/// Displays detailed information about a SampleItem. + +class WidgetWall extends ConsumerStatefulWidget { + const WidgetWall({super.key}); + + @override + ConsumerState createState() => _WidgetWallState(); +} + +class _WidgetWallState extends ConsumerState { + Future> buildList() async { + ref.watch(homeControllerProvider); + //var itemCount = 0; + + final items = ref.watch(itemsProvider); + + // ignore: unused_local_variable + final val = items.value; + + return []; + } + + @override + Widget build(BuildContext context) { + // this.buildList(); + final controller = ref.watch(itemsProvider); + switch (controller) { + case AsyncError(:final error): + return Text('Error: $error'); + case AsyncData(:final value): + final allItems = value; + final items = []; + for (var i = 0; i < allItems.length; i++) { + // ignore: avoid_dynamic_calls + if (allItems.elementAt(i).isVisible == true) { + logger.i('INSERTING VISIBLE ITEM'); + items.add(allItems.elementAt(i)); + } + } + final itemWidgets = []; + final itemCount = items.length; + for (var i = 0; i < itemCount; i++) { + final item = items.elementAt(i); + if (item is Hydration && item.isVisible) { + final itemwidget = CompoundWidgetWater(item: item); + itemWidgets.insert(i, itemwidget); + } else if (item is TimerItem && item.isVisible) { + if (item.isVisible == true) { + logger.i('VISIBLE'); + final itemwidget = CompoundWidgetTimer(item: item); + itemWidgets.insert(i, itemwidget); + } else { + logger.i('IS NOT VISIBLE'); + } + } else if (item is TasksItem && item.isVisible) { + final itemwidget = CompoundWidgetTasks(item: item); + itemWidgets.insert(i, itemwidget); + } + } + return Scaffold( + appBar: AppBar( + iconTheme: IconThemeData( + color: Theme.of(context).colorScheme.onPrimary, + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + foregroundColor: Theme.of(context).colorScheme.primary, + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + textScaler: const TextScaler.linear(2), + 'Habitrack ', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSecondary, + // fontStyle: FontStyle.italic, + ), + ), + const Align( + alignment: Alignment.topRight, + child: AddWidgetButton(), + ), + ], + ), + ), + body: ColoredBox( + color: Theme.of(context).colorScheme.primaryContainer, + child: ListView( + padding: const EdgeInsets.all(8), + children: itemWidgets, + ), + ), + ); + + default: + return const CircularProgressIndicator(); + } + } +} diff --git a/app/lib/l10n/app_de.arb b/app/lib/l10n/app_de.arb new file mode 100644 index 0000000..0d0c6c3 --- /dev/null +++ b/app/lib/l10n/app_de.arb @@ -0,0 +1,59 @@ +{ + + + "settingsHeader": "Einstellungen", + + "widgetSettings_durationPickerCurrent": "Derzeitige Laufzeit:", + "widgetSettings_durationPickerButton": "Laufzeit auswählen", + + "addWidgetHeader": "Widget Hinzufügen", + "addWidget_water": "Wasser-Buddy hinzufügen", + "addWidget_timer": "Fokus-Timer hinzufügen", + "addWidget_tasks": "Tasks Widget hinzufügen", + + "widgetSettings_name": "Name des Widgets", + "widgetSettings_saveButton": "Speichern", + "widgetSettings_deleteButton": "Löschen", + "widgetSettings_cancelButton": "Abbrechen", + + "waterWidget_defaultName": "Trink-Buddy", + "waterWidgetSettings_button1": "Button 1 in ml", + "waterWidgetSettings_button2": "Button 2 in ml", + "waterWidgetSettings_goal": "Tagesziel", + "waterWidgetSettings_current": "Derzeit getrunken", + + "waterWidget_customAmountButton": "+ Beliebig", + "waterWidget_customAmountMessage": "Geben sie die Geünschte Wassermenge ein:", + + "tasksWidget_defaultName": "Tasks Widget", + "tasksWidget_addTaskButtonLabel": "Aufgabe Hinzufügen", + + "tasksWidget_editTask": "Aufgabe bearbeiten", + + "taskDefaultName": "Neue Aufgabe", + "taskSettings_name": "Aufgabenname", + "taskSettings_due": "Fällig am", + "taskSettings_duePicker": "Fälligkeitsdatum auswählen", + + "tasksWidget_overdue": "Überfällig!", + + "tasksWidget_deleteDone": "Erledigte Aufgaben löschen", + "tasksWidget_rescheduleOverdue": "Überfällige Aufgaben neu planen", + + "timerWidget_defaultName": "Fokus-Timer", + "timerWidget_buttonStart": "Starten", + "timerWidget_buttonPause": "Anhalten", + "timerWidget_buttonContinue": "Weiter", + "timerWidget_buttonReset": "Zurücksetzen", + + "timerWidgetSettings_duration": "Tägliches Ziel", + + "timerWidget_current": "Jetzt", + "timerWidget_goal": "Ziel", + + "timerWidget_pausedForEdit": "Halte den Timer an, um ihn bearbeiten zu können!" + + + +} + diff --git a/app/lib/l10n/app_en.arb b/app/lib/l10n/app_en.arb new file mode 100644 index 0000000..9ac80c7 --- /dev/null +++ b/app/lib/l10n/app_en.arb @@ -0,0 +1,64 @@ +{ + + + + "settingsHeader": "Settings", + + "widgetSettings_durationPickerCurrent": "Current Duration:", + "widgetSettings_durationPickerButton": "Choose Duration", + + "addWidgetHeader": "Widget Picker", + "addWidget_water": "Add Hydration Tracker", + "addWidget_timer": "Add Focus Timer", + "addWidget_tasks": "Add Tasks Widget", + + "widgetSettings_name": "Widget Name", + "widgetSettings_saveButton": "Confirm", + "widgetSettings_deleteButton": "Delete", + "widgetSettings_cancelButton": "Cancel", + + "waterWidget_defaultName": "Hydration Tracker", + "waterWidgetSettings_button1": "Button 1 in ml", + "waterWidgetSettings_button2": "Button 2 in ml", + "waterWidgetSettings_goal": "Daily Goal", + "waterWidgetSettings_current": "Current Amount", + + "waterWidget_customAmountButton": "+ custom", + "waterWidget_customAmountMessage": "Enter an amount as desired:", + + + "tasksWidget_defaultName": "Tasks Widget", + "tasksWidget_addTaskButtonLabel": "Add Task", + + "tasksWidget_editTask": "Edit Task", + + "taskDefaultName": "Examplar Task", + "taskSettings_name": "Task Name", + "taskSettings_due": "Due Date", + "taskSettings_duePicker": "Choose a due date", + + "tasksWidget_overdue": "Overdue!", + + "tasksWidget_deleteDone": "Delete completed tasks", + "tasksWidget_rescheduleOverdue": "Reschedule overdue tasks", + + + "timerWidget_defaultName": "Focus Timer", + "timerWidget_buttonStart": "Start", + "timerWidget_buttonPause": "Stop", + "timerWidget_buttonContinue": "Continue", + "timerWidget_buttonReset": "Reset", + + "timerWidgetSettings_duration": "Daily Goal", + + "timerWidget_current": "Current", + "timerWidget_goal": "Goal", + + "timerWidget_pausedForEdit": "Timer must be paused before editing!" + + + + + +} + diff --git a/app/lib/main.dart b/app/lib/main.dart new file mode 100644 index 0000000..51cc548 --- /dev/null +++ b/app/lib/main.dart @@ -0,0 +1,86 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_background/flutter_background.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/infrastructure/routing.dart'; +import 'package:habitrack_app/sembast/global_providers.dart'; +import 'package:logger/logger.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:sembast/sembast_io.dart'; +import 'package:sembast_web/sembast_web.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + if (kIsWeb) { + final factory = databaseFactoryWeb; + final db = await factory.openDatabase('test'); + runApp( + ProviderScope( + overrides: [databaseProvider.overrideWithValue(db)], + child: const MainApp(), + ), + ); + // running on the web! + } else { + final appPath = await getApplicationDocumentsDirectory(); + + appPath.createSync(recursive: true); + final dbPath = join(appPath.path, 'widgets.db'); + final database = await databaseFactoryIo.openDatabase(dbPath); + const androidConfig = FlutterBackgroundAndroidConfig( + notificationTitle: 'flutter_background example app', + notificationText: + // ignore: lines_longer_than_80_chars + 'Background notification for keeping the example app running in the background', + notificationIcon: AndroidResource( + name: 'background_icon', + // ignore: avoid_redundant_argument_values + defType: 'drawable', + ), // Default is ic_launcher from folder mipmap + ); + + await FlutterBackground.initialize(androidConfig: androidConfig); + await FlutterBackground.enableBackgroundExecution(); + + runApp( + ProviderScope( + overrides: [ + databaseProvider.overrideWithValue(database), + ], + child: const MainApp(), + ), + ); + } +} + +Logger logger = Logger(); + +class MainApp extends StatelessWidget { + const MainApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp.router( + title: 'Habitrack', + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + debugShowCheckedModeBanner: false, + theme: ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: Colors.purple, + ), + textTheme: const TextTheme( + displayLarge: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.amber, + ), + ), + ), + routerConfig: goRouter, + ); + } +} diff --git a/app/lib/pages/dashboard_hydration_subpage.dart b/app/lib/pages/dashboard_hydration_subpage.dart new file mode 100644 index 0000000..83cd162 --- /dev/null +++ b/app/lib/pages/dashboard_hydration_subpage.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:habitrack_app/pages/hydration_graph_widget.dart'; + +class SubpageHydrationButton extends StatelessWidget { + const SubpageHydrationButton({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 300, + margin: const EdgeInsets.only(top: 20, bottom: 7.5), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + minimumSize: const Size(10, 70), + ), + onPressed: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DashboardHydrationSubpage(), + ), + ), + }, + child: Row( + children: [ + Icon( + Icons.local_drink, + color: Theme.of(context).colorScheme.onPrimary, + ), + Text( + ' Hydration Widgets', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ); + } +} + +class DashboardHydrationSubpage extends StatefulWidget { + const DashboardHydrationSubpage({super.key}); + + @override + State createState() => _DashboardHydrationSubpageState(); +} + +class _DashboardHydrationSubpageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + appBar: AppBar( + iconTheme: IconThemeData( + color: Theme.of(context).colorScheme.onPrimary, + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + foregroundColor: Theme.of(context).colorScheme.primary, + title: Text( + 'Statistics: Hydration Widgets', + textScaler: const TextScaler.linear(1.2), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + body: const HydrationGraphWidget(), + ); + } +} diff --git a/app/lib/pages/dashboard_page.dart b/app/lib/pages/dashboard_page.dart new file mode 100644 index 0000000..ac8e2f3 --- /dev/null +++ b/app/lib/pages/dashboard_page.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/pages/dashboard_hydration_subpage.dart'; +import 'package:habitrack_app/pages/dashboard_task_subpage.dart'; +import 'package:habitrack_app/pages/dashboard_timer_subpage.dart'; +import 'package:habitrack_app/pages/reset_subpage.dart'; + +class DashboardPage extends ConsumerStatefulWidget { + const DashboardPage({super.key}); + @override + ConsumerState createState() => _DashboardPageState(); +} + +class _DashboardPageState extends ConsumerState { + @override + Widget build(BuildContext context) { + //final items = ref.watch(itemsProvider); + + //final len = items.value!.length; + return Scaffold( + appBar: AppBar( + title: Text( + 'Dashboard', + textScaler: const TextScaler.linear(1.4), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onSecondary, + ), + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + ), + body: ColoredBox( + color: Theme.of(context).colorScheme.primaryContainer, + child: SizedBox( + width: MediaQuery.of(context).size.width, + child: const Column( + children: [ + SubpageHydrationButton(), + SubpageTaskButton(), + SubpageTimerButton(), + ResetSubpageButton(), + ], + ), + ), + ), + ); + } +} diff --git a/app/lib/pages/dashboard_task_subpage.dart b/app/lib/pages/dashboard_task_subpage.dart new file mode 100644 index 0000000..66c4984 --- /dev/null +++ b/app/lib/pages/dashboard_task_subpage.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:habitrack_app/pages/tasks_graph_widget.dart'; + +class SubpageTaskButton extends StatelessWidget { + const SubpageTaskButton({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 300, + margin: const EdgeInsets.only(top: 7.5, bottom: 7.5), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + minimumSize: const Size(10, 70), + ), + onPressed: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DashboardTaskSubpage(), + ), + ), + }, + child: Row( + children: [ + Icon( + Icons.task, + color: Theme.of(context).colorScheme.onPrimary, + ), + Text( + ' Task Widgets', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ); + } +} + +class DashboardTaskSubpage extends StatefulWidget { + const DashboardTaskSubpage({super.key}); + + @override + State createState() => _DashboardTaskSubpageState(); +} + +class _DashboardTaskSubpageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + appBar: AppBar( + iconTheme: IconThemeData( + color: Theme.of(context).colorScheme.onPrimary, + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + foregroundColor: Theme.of(context).colorScheme.primary, + title: Text( + 'Statistics: Tasks Widgets', + textScaler: const TextScaler.linear(1.2), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + body: TasksGraphWidget(), + ); + } +} diff --git a/app/lib/pages/dashboard_timer_subpage.dart b/app/lib/pages/dashboard_timer_subpage.dart new file mode 100644 index 0000000..432f0ee --- /dev/null +++ b/app/lib/pages/dashboard_timer_subpage.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:habitrack_app/pages/timer_graph_widget.dart'; + +class SubpageTimerButton extends StatelessWidget { + const SubpageTimerButton({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 300, + margin: const EdgeInsets.only(top: 7.5, bottom: 7.5), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + minimumSize: const Size(10, 70), + ), + onPressed: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const DashboardTimerSubpage(), + ), + ), + }, + child: Row( + children: [ + Icon( + Icons.timer, + color: Theme.of(context).colorScheme.onPrimary, + ), + Text( + ' Timer Widgets', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ); + } +} + +class DashboardTimerSubpage extends StatefulWidget { + const DashboardTimerSubpage({super.key}); + + @override + State createState() => _DashboardTimerSubpageState(); +} + +class _DashboardTimerSubpageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Theme.of(context).colorScheme.primaryContainer, + appBar: AppBar( + iconTheme: IconThemeData( + color: Theme.of(context).colorScheme.onPrimary, + ), + backgroundColor: Theme.of(context).colorScheme.secondary, + foregroundColor: Theme.of(context).colorScheme.primary, + title: Text( + 'Statistics: Timer Widgets', + textScaler: const TextScaler.linear(1.2), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + body: TimerGraphWidget(), + ); + } +} diff --git a/app/lib/pages/hydration_graph_widget.dart b/app/lib/pages/hydration_graph_widget.dart new file mode 100644 index 0000000..84e26ca --- /dev/null +++ b/app/lib/pages/hydration_graph_widget.dart @@ -0,0 +1,478 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/hydration.dart'; +import 'package:intl/intl.dart'; + +class DataPoint { + DataPoint({required this.date, required this.progress}); + DateTime date; + double progress; +} + +class HydrationGraphWidget extends ConsumerStatefulWidget { + const HydrationGraphWidget({super.key}); + + @override + ConsumerState createState() => + _TasksGraphWidgetState(); +} + +class _TasksGraphWidgetState extends ConsumerState { + void _buildList(List value) { + final items = value; + _thisWeekCompleted = 0; + _thisWeekPlanned = 0; + _maxAmount = 0; + + setState(() { + _todayCompleted = 0; + _todayPlanned = 0; + _weeklyPlannedEntries = []; + _weeklyWorkedEntries = []; + for (var i = 0; i <= 6; i++) { + final itemToInsert = DataPoint( + date: _latestDate.subtract(Duration(days: 6 - i)), + progress: 0, + ); + + _weeklyPlannedEntries.add(itemToInsert); + } + + for (var i = 0; i <= 6; i++) { + final itemToInsert = DataPoint( + date: _latestDate.subtract(Duration(days: 6 - i)), + progress: 0, + ); + _weeklyWorkedEntries.add(itemToInsert); + } + }); + _thisWeekPlanned = 0; + + for (final item in items) { + if (_selectedValue == 'weekly') { + var alreadyAdded = false; + for (var i = 0; i <= 6; i++) { + DateTime parsedDate; + + if (item is Hydration) { + if (item.current / item.goal >= 1 && item.completedOn != '') { + //item is completed + parsedDate = DateTime.parse(item.completedOn); + } else { + parsedDate = DateTime.parse(item.createdOn); + logger.i('GOAL: $item.goal'); + } + + logger + .i('BEFORE $i days ago and AFTER ${i + 1} days ago, element at ' + 'index ${6 - i} will be updated.'); + + if (parsedDate.isBefore(_latestDate.subtract(Duration(days: i))) && + parsedDate + .isAfter(_latestDate.subtract(Duration(days: i + 1)))) { + logger.i('LOOPING'); + if (!alreadyAdded) { + alreadyAdded = true; + _thisWeekCompleted += item.current / 1000; + + _thisWeekPlanned += item.goal / 1000; + } + + if (item.goal > _maxAmount) { + _maxAmount = (item.goal.toDouble() / 1000).ceilToDouble(); + } + if (item.current > _maxAmount && item.current > item.goal) { + _maxAmount = (item.current.toDouble() / 1000).ceilToDouble(); + } + // Update maxAmount + setState(() { + _weeklyPlannedEntries.elementAt(6 - i).progress = + item.goal.toDouble() / 1000; + _weeklyWorkedEntries.elementAt(6 - i).progress = + item.current.toDouble() / 1000; + }); + } + } + } + } else if (_selectedValue == 'daily' && item is Hydration) { + logger.i('DATES'); + final parsedDate = DateTime.parse(item.createdOn); + logger.i('LATEST DATE: $_latestDate'); + + final oneDayAgo = _latestDate.subtract(const Duration(days: 1)); + + if (parsedDate.isBefore(oneDayAgo)) { + logger.i('More than a day old'); + } else if (parsedDate.isAfter(oneDayAgo) && + parsedDate.isBefore(_latestDate)) { + logger.i('TOday'); + _todayCompleted = item.current / 1000; + _todayPlanned = item.goal / 1000; + } + } + } + } + + void _showPreviousWeek() { + setState(() { + _todayPlanned = 0; + _todayCompleted = 0; + _thisWeekPlanned = 0; + _thisWeekCompleted = 0; + }); + + if (_selectedValue == 'weekly') { + _latestDate = _latestDate.subtract(const Duration(days: 7)); + } else if (_selectedValue == 'daily') { + logger.i('HMMM'); + _latestDate = _latestDate.subtract(const Duration(days: 1)); + } + final items = ref.watch(itemsProvider); + + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } // get current date + // show past 7 days starting 14 days ago + } + + void _showNextWeek() { + if (_selectedValue == 'weekly') { + _latestDate = _latestDate.add(const Duration(days: 7)); + } else if (_selectedValue == 'daily') { + logger.i('HMMM'); + _latestDate = _latestDate.add(const Duration(days: 1)); + } + if (!_latestDate.isAfter(DateTime.now())) { + final items = ref.watch(itemsProvider); + + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } // get current date + } + + // show past 7 days starting 14 days ago + } + + String _getText() { + final dateFormat = DateFormat('dd. MMM yyyy'); + + if (_selectedValue == 'weekly' && + _latestDate.isAfter(DateTime.now().subtract(const Duration(days: 6)))) { + return ' this past week'; + } else if (_selectedValue == 'weekly' && + !_latestDate + .isAfter(DateTime.now().subtract(const Duration(days: 6)))) { + return ' ' + 'from' + ' ${dateFormat.format(_latestDate.subtract(const Duration(days: 6)))}' + ' to ${dateFormat.format(_latestDate)}'; + } + final formattedDate = dateFormat.format(_latestDate); + // return ' on ${_latestDate.toString().substring(6, 10)}'; + return ' on $formattedDate'; + } + + List thisWeekItems = []; + List todayItems = []; + String? _selectedValue = 'weekly'; + + double _thisWeekCompleted = 0; + double _thisWeekPlanned = 0; + double _maxAmount = 0; + DateTime _latestDate = DateTime.now(); + double _todayCompleted = 0; + double _todayPlanned = 0; + + List _weeklyPlannedEntries = []; + List _weeklyWorkedEntries = []; + + @override + Widget build(BuildContext context) { + final items = ref.watch(itemsProvider); + + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } + + final firstDate = _weeklyPlannedEntries.elementAtOrNull(0)!.date; + logger.i('HMMM $firstDate'); + final flSpots = []; + for (final dataPoint in _weeklyPlannedEntries) { + final xValue = _daysBetween(_weeklyPlannedEntries[0].date, dataPoint.date) + .toDouble(); + + final lcbd = FlSpot( + xValue, + dataPoint.progress, + ); + + flSpots.add(lcbd); + } + + final weeklyWorkedSpots = []; + for (final dataPoint in _weeklyWorkedEntries) { + final xValue = + _daysBetween(_weeklyWorkedEntries[0].date, dataPoint.date).toDouble(); + final lcbd = FlSpot(xValue, dataPoint.progress); + + weeklyWorkedSpots.add(lcbd); + } + final gradientColors = [ + Colors.cyan, + Colors.blueAccent, + ]; + final gradient2Colors = [ + Colors.amber, + Colors.amberAccent, + ]; + final df = DateFormat('dd. MMMM'); + + return Column( + children: [ + Container( + height: 75, + color: Theme.of(context).colorScheme.primaryContainer, + margin: const EdgeInsets.only(bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: _showPreviousWeek, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + child: const Text('Previous'), + ), + DropdownButton( + value: _selectedValue, + borderRadius: BorderRadius.circular(10), + onChanged: (value) { + setState(() { + _latestDate = DateTime.now(); + _selectedValue = value; + }); + }, + items: const [ + DropdownMenuItem( + value: 'daily', + child: Text('Daily'), + ), + DropdownMenuItem( + value: 'weekly', + child: Text('Weekly'), + ), + ], + ), + ElevatedButton( + onPressed: _showNextWeek, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + child: const Text('Next'), + ), + ], + ), + ), + if (_selectedValue == 'weekly') ...[ + Container( + padding: + const EdgeInsets.only(bottom: 15, top: 15, right: 15, left: 5), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + height: MediaQuery.of(context).size.height * 0.5, + child: LineChart( + LineChartData( + minY: 0, + maxY: _maxAmount, + lineBarsData: [ + LineChartBarData( + isCurved: true, + preventCurveOverShooting: true, + spots: flSpots, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradientColors + .map((color) => color.withOpacity(0.6)) + .toList(), + ), + ), + ), + LineChartBarData( + color: Colors.amber, + isCurved: true, + isStrokeCapRound: true, + preventCurveOverShooting: true, + spots: weeklyWorkedSpots, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradient2Colors + .map((color) => color.withOpacity(0.6)) + .toList(), + ), + ), + ), + ], + titlesData: FlTitlesData( + rightTitles: const AxisTitles( + sideTitles: SideTitles( + reservedSize: 30, + interval: 20, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles( + interval: 20, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 30, + getTitlesWidget: (value, meta) => Text( + '$value l', + style: const TextStyle( + fontSize: 10, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + ), + ), + // interval: 0.25, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + getTitlesWidget: (value, meta) => Text( + style: const TextStyle( + fontSize: 10, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + ), + df.format( + DateTime( + firstDate.year, + firstDate.month, + firstDate.day, + ).add(Duration(days: value.round())), + ), + ), + showTitles: true, + interval: 1, + ), + ), + ), + ), + ), + ), + ), + ], + if (_selectedValue == 'daily') ...[ + SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + height: MediaQuery.of(context).size.height * 0.5, + child: PieChart( + PieChartData( + sectionsSpace: 0, + sections: [ + PieChartSectionData( + value: _todayCompleted, // Progress + color: Theme.of(context).colorScheme.primaryFixedDim, + radius: 60, + title: (_todayPlanned > 0) + // ignore: lines_longer_than_80_chars + ? '${((_todayCompleted / _todayPlanned) * 100).floorToDouble()} %' + : '0 %', + + titleStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.secondary, + ), + ), + PieChartSectionData( + titleStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primaryFixedDim, + ), + value: (_todayPlanned > 0 && _todayCompleted > 0) + ? (_todayPlanned - _todayCompleted) + : 1, // Total - progress + color: Theme.of(context).colorScheme.secondary, + radius: 60, + showTitle: _todayPlanned == 0 || + (_todayPlanned > 0 && _todayCompleted == 0), + title: '0 %', + ), + ], + ), + ), + ), + ], + SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + child: Column( + children: [ + Text( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + // ignore: lines_longer_than_80_chars + '${(_selectedValue == 'weekly') ? _thisWeekCompleted : _todayCompleted} liters drunk${_getText()}', + ), + Text( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + // ignore: lines_longer_than_80_chars + 'Out of a total goal of ${(_selectedValue == 'weekly') ? _thisWeekPlanned : _todayPlanned} liters', + ), + ], + ), + ), + ], + ); + } + + int _daysBetween(DateTime from, DateTime to) { + final d1 = DateTime(from.year, from.month, from.day); + final d2 = DateTime(to.year, to.month, to.day); + return (d2.difference(d1).inHours / 24).round(); + } +} diff --git a/app/lib/pages/reset_subpage.dart b/app/lib/pages/reset_subpage.dart new file mode 100644 index 0000000..3b802ee --- /dev/null +++ b/app/lib/pages/reset_subpage.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/sembast/hydration.dart'; +import 'package:habitrack_app/sembast/tasks_list.dart'; +import 'package:habitrack_app/sembast/timer.dart'; + +class ResetSubpageButton extends ConsumerStatefulWidget { + const ResetSubpageButton({super.key}); + + @override + ConsumerState createState() => _ResetSubpageButtonState(); +} + +class _ResetSubpageButtonState extends ConsumerState { + Future _confirmPopup() async { + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Theme.of(context).colorScheme.onPrimary, + title: Text( + 'Are you sure?', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontWeight: FontWeight.bold, + fontSize: 25, + ), + ), + scrollable: true, + actions: [ + OutlinedButton( + style: OutlinedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + 'Cancel', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + onPressed: () { + final controller = ref.watch(homeControllerProvider); + final items = ref.watch(itemsProvider); + switch (items) { + case AsyncData(:final value): + final items = value; + for (var i = 0; i < items.length; i++) { + final item = items.elementAt(i); + if (item is Hydration) { + controller.delete(item.id); + } else if (item is TasksItem) { + controller.delete(item.id); + } else if (item is TimerItem) { + controller.delete(item.id); + } + } + Navigator.of(context).pop(); + } + //DateTime due = DateTime.parse(formattedDate); + // ignore: cascade_invocations + }, + child: Text( + 'Yes', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 300, + margin: const EdgeInsets.only(top: 7.5, bottom: 7.5), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + minimumSize: const Size(10, 70), + ), + onPressed: _confirmPopup, + child: Row( + children: [ + Icon( + Icons.delete, + color: Theme.of(context).colorScheme.onPrimary, + ), + Text( + 'Clear Database', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimary, + ), + ), + ], + ), + ), + ); + } +} diff --git a/app/lib/pages/tasks_graph_widget.dart b/app/lib/pages/tasks_graph_widget.dart new file mode 100644 index 0000000..a267b71 --- /dev/null +++ b/app/lib/pages/tasks_graph_widget.dart @@ -0,0 +1,687 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/tasks_list.dart'; +import 'package:intl/intl.dart'; + +class DataPoint { + DataPoint({required this.date, required this.progress}); + DateTime date; + double progress; +} + +class TasksGraphWidget extends ConsumerStatefulWidget { + TasksGraphWidget({super.key}); + + @override + ConsumerState createState() => + _TasksGraphWidgetState(); + + final entries = [ + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + ]; +} + +class _TasksGraphWidgetState extends ConsumerState { + void _buildList(List value) { + final items = value; + _tasksCompleted = 0; + _tasksPlanned = 0; + _weeklyOverdue = 0; + _totalTasksCompleted = 0; + _totalTasksPlanned = 0; + _maxTasks = 0; + _weeklyPlannedEntries = []; + _weeklyOverdueEntries = []; + _todayOverdue = 0; + + _totalOverdue = 0; + const seperator = '_SEPARATOR_'; + + setState(() { + _todayCompleted = 0; + _todayPlanned = 0; + }); + for (var i = 0; i <= 6; i++) { + final itemToInsert = DataPoint( + date: _latestDate.subtract(Duration(days: 6 - i)), + progress: 0, + ); + + _weeklyPlannedEntries.add(itemToInsert); + } + + _weeklyWorkedEntries = []; + + for (var i = 0; i <= 6; i++) { + final itemToInsert = DataPoint( + date: _latestDate.subtract(Duration(days: 6 - i)), + progress: 0, + ); + _weeklyWorkedEntries.add(itemToInsert); + } + + for (var i = 0; i <= 6; i++) { + final itemToInsert = DataPoint( + date: _latestDate.subtract(Duration(days: 6 - i)), + progress: 0, + ); + _weeklyOverdueEntries.add(itemToInsert); + } + + for (final item in items) { + if (_selectedValue == 'weekly' && item is TasksItem) { + logger.i('HMM'); + + _tasksCompleted = 0; + + for (final individualToDo in item.completedTaskList) { + final toConvert = individualToDo.split(seperator); + final parsedDate = DateTime.parse(toConvert.elementAtOrNull(2)!); + + var alreadyAdded = false; + + for (var i = 0; i <= 6; i++) { + if (parsedDate.isBefore(_latestDate.subtract(Duration(days: i))) && + parsedDate + .isAfter(_latestDate.subtract(Duration(days: i + 1)))) { + logger.i('LOOPING'); + if (!alreadyAdded) { + alreadyAdded = true; + _tasksCompleted += 1; + _totalTasksCompleted += 1; + } + + // Update maxAmount + setState(() { + logger.i('COMPLETED: $_tasksCompleted'); + _weeklyWorkedEntries.elementAt(6 - i).progress = + _tasksCompleted.toDouble(); + _weeklyPlannedEntries.elementAt(6 - i).progress = + _tasksPlanned.toDouble() + + _tasksCompleted.toDouble() + + _weeklyOverdue; + }); + } + } + } + _tasksPlanned = 0; + _weeklyOverdue = 0; + + for (final individualToDo in item.taskList) { + final toConvert = individualToDo.split(seperator); + final due = DateTime.parse(toConvert.elementAtOrNull(1)!); + + final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!); + + final parsedDate = completedOn; + + var alreadyAdded = false; + logger.i('NANI'); + + for (var i = 0; i <= 6; i++) { + if (parsedDate.isBefore(_latestDate.subtract(Duration(days: i))) && + parsedDate + .isAfter(_latestDate.subtract(Duration(days: i + 1)))) { + logger.i('LOOPING'); + + // Update maxAmount + setState(() { + if (!alreadyAdded) { + alreadyAdded = true; + final now = DateTime.now(); + // _maxTasks += 1; + if (DateTime(now.year, now.month, now.day).isAfter(due)) { + logger + ..i('OVERDUE TASK') + ..i('NOW: $now') + ..i('DUE: $due'); + _totalOverdue += 1; + _weeklyOverdue += 1; + _weeklyOverdueEntries.elementAt(6 - i).progress = + _weeklyOverdue; + } else { + logger.i('Task is NOT overdue'); + _tasksPlanned += 1; + _totalTasksPlanned += 1; + } + } + _weeklyPlannedEntries.elementAt(6 - i).progress = + _tasksPlanned.toDouble() + + _tasksCompleted.toDouble() + + _weeklyOverdue; + }); + } + } + logger + ..i('TASKS PLANNED FOR THIS DAY: $_tasksPlanned') + ..i('TASKS COMPLETED FOR THIS DAY: $_tasksCompleted') + ..i('TOTAL TASKS: ${_tasksPlanned + _tasksCompleted}'); + + if (_tasksPlanned + _tasksCompleted + _weeklyOverdue > _maxTasks) { + _maxTasks = (_tasksPlanned + _tasksCompleted + _weeklyOverdue) + .ceilToDouble(); + logger.i('MAX TASKS: $_maxTasks'); + } + } + } else if (_selectedValue == 'daily' && item is TasksItem) { + for (final individualToDo in item.completedTaskList) { + final toConvert = individualToDo.split(seperator); + + final completedOn = DateTime.parse(toConvert.elementAtOrNull(3)!); + + final completed = + toConvert.elementAtOrNull(4)!.toLowerCase() == 'true'; + + final oneDayAgo = _latestDate.subtract(const Duration(days: 1)); + final parsedDate = completedOn; + if (parsedDate.isBefore(oneDayAgo)) { + logger.i('More than a day old'); + } else if (parsedDate.isAfter(oneDayAgo) && + parsedDate.isBefore(_latestDate)) { + logger.i('TOday'); + if (completed) { + _todayCompleted += 1; + _todayPlanned += 1; + } else { + _todayPlanned += 1; + } + } + } + for (final individualToDo in item.taskList) { + final toConvert = individualToDo.split(seperator); + final due = DateTime.parse(toConvert.elementAtOrNull(1)!); + + final createdOn = DateTime.parse(toConvert.elementAtOrNull(2)!); + + final completed = + toConvert.elementAtOrNull(4)!.toLowerCase() == 'true'; + + final oneDayAgo = _latestDate.subtract(const Duration(days: 1)); + final parsedDate = createdOn; + if (parsedDate.isBefore(oneDayAgo)) { + logger.i('More than a day old'); + } else if (parsedDate.isAfter(oneDayAgo) && + parsedDate.isBefore(_latestDate)) { + logger.i('TOday'); + if (completed) { + _todayCompleted += 1; + _todayPlanned += 1; + } else { + _todayPlanned += 1; + final now = DateTime.now(); + if (DateTime(now.year, now.month, now.day).isAfter(due)) { + logger + ..i('OVERDUE TASK') + ..i('NOW: $now') + ..i('DUE: $due'); + _totalOverdue += 1; + _todayOverdue += 1; + } + } + } + } + } + } + } + + void _showPreviousWeek() { + if (_selectedValue == 'weekly') { + _latestDate = _latestDate.subtract(const Duration(days: 7)); + } else if (_selectedValue == 'daily') { + _latestDate = _latestDate.subtract(const Duration(days: 1)); + } + final items = ref.watch(itemsProvider); + + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } // get current date + // show past 7 days starting 14 days ago + } + + void _showNextWeek() { + if (_selectedValue == 'weekly') { + _latestDate = _latestDate.add(const Duration(days: 7)); + } else if (_selectedValue == 'daily') { + _latestDate = _latestDate.add(const Duration(days: 1)); + } + if (!_latestDate.isAfter(DateTime.now())) { + final items = ref.watch(itemsProvider); + + ref.watch(homeControllerProvider); + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } // get current date + } + + // show past 7 days starting 14 days ago + } + + String _getText() { + final dateFormat = DateFormat('dd. MMM yyyy'); + + if (_selectedValue == 'weekly' && + _latestDate.isAfter(DateTime.now().subtract(const Duration(days: 6)))) { + return ' this past week'; + } else if (_selectedValue == 'weekly' && + !_latestDate + .isAfter(DateTime.now().subtract(const Duration(days: 6)))) { + return ' ' + 'from' + ' ${dateFormat.format(_latestDate.subtract(const Duration(days: 6)))}' + ' to ${dateFormat.format(_latestDate)}'; + } + final formattedDate = dateFormat.format(_latestDate); + // return ' on ${_latestDate.toString().substring(6, 10)}'; + return ' on $formattedDate'; + } + + final gradientColors = [ + Colors.cyan, + Colors.blueAccent, + ]; + final gradient2Colors = [ + Colors.amber, + Colors.amberAccent, + ]; + final gradient3Colors = [ + Colors.deepPurple, + Colors.deepPurpleAccent, + ]; + List thisWeekItems = []; + List todayItems = []; + String? _selectedValue = 'weekly'; + + int _tasksCompleted = 0; + int _tasksPlanned = 0; + int _totalTasksCompleted = 0; + int _totalTasksPlanned = 0; + double _maxTasks = 0; + DateTime _latestDate = DateTime.now(); + double _todayCompleted = 0; + double _todayPlanned = 0; + double _todayOverdue = 0; + double _weeklyOverdue = 0; + double _totalOverdue = 0; + + List _weeklyPlannedEntries = []; + List _weeklyWorkedEntries = []; + List _weeklyOverdueEntries = []; + + @override + Widget build(BuildContext context) { + final items = ref.watch(itemsProvider); + + // ignore: unused_element + double getProgress() { + if (_todayPlanned == 0) { + return 0; + } + logger.i( + 'GLORIOUS PROGRESS${_todayCompleted / _todayPlanned}', + ); + return _todayCompleted; // - this._todayCompleted; + } + + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } + + final firstDate = _weeklyPlannedEntries.elementAtOrNull(0)!.date; + final flSpots = []; + for (final dataPoint in _weeklyPlannedEntries) { + final xValue = _daysBetween(_weeklyPlannedEntries[0].date, dataPoint.date) + .toDouble(); + + final lcbd = FlSpot( + xValue, + dataPoint.progress, + ); + + flSpots.add(lcbd); + } + + final weeklyWorkedSpots = []; + for (final dataPoint in _weeklyWorkedEntries) { + final xValue = + _daysBetween(_weeklyWorkedEntries.elementAt(0).date, dataPoint.date) + .toDouble(); + final lcbd = FlSpot(xValue, dataPoint.progress); + + weeklyWorkedSpots.add(lcbd); + } + + final weeklyOverdueSpots = []; + for (final dataPoint in _weeklyOverdueEntries) { + final xValue = _daysBetween(_weeklyOverdueEntries[0].date, dataPoint.date) + .toDouble(); + final lcbd = FlSpot(xValue, dataPoint.progress); + weeklyOverdueSpots.add(lcbd); + // weeklyWorkedSpots.add(lcbd); + } + + final df = DateFormat('dd. MMMM'); + + return Column( + children: [ + Container( + height: 75, + color: Theme.of(context).colorScheme.primaryContainer, + margin: const EdgeInsets.only(bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: _showPreviousWeek, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + child: const Text('Previous'), + ), + DropdownButton( + value: _selectedValue, + borderRadius: BorderRadius.circular(10), + onChanged: (value) { + setState(() { + _latestDate = DateTime.now(); + _selectedValue = value; + }); + }, + items: const [ + DropdownMenuItem( + value: 'daily', + child: Text('Daily'), + ), + DropdownMenuItem( + value: 'weekly', + child: Text('Weekly'), + ), + ], + ), + ElevatedButton( + onPressed: _showNextWeek, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + child: const Text('Next'), + ), + ], + ), + ), + if (_selectedValue == 'weekly') ...[ + Container( + // color: Theme.of(context).colorScheme.primaryContainer, + padding: + const EdgeInsets.only(bottom: 15, top: 15, right: 15, left: 5), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + height: MediaQuery.of(context).size.height * 0.5, + child: LineChart( + LineChartData( + minY: 0, + maxY: _maxTasks, + lineBarsData: [ + LineChartBarData( + isCurved: true, + preventCurveOverShooting: true, + spots: flSpots, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradientColors + .map((color) => color.withOpacity(0.6)) + .toList(), + ), + ), + ), + LineChartBarData( + color: Colors.amber, + isCurved: true, + // isStrokeCapRound: true, + preventCurveOverShooting: true, + spots: weeklyWorkedSpots, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradient2Colors + .map((color) => color.withOpacity(0.6)) + .toList(), + ), + ), + ), + LineChartBarData( + color: Colors.purple, + isCurved: true, + isStrokeCapRound: true, + preventCurveOverShooting: true, + spots: weeklyOverdueSpots, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradient3Colors + .map((color) => color.withOpacity(0.6)) + .toList(), + ), + ), + ), + ], + titlesData: FlTitlesData( + rightTitles: const AxisTitles( + sideTitles: SideTitles( + reservedSize: 30, + interval: 20, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles( + interval: 20, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 50, + getTitlesWidget: (value, meta) => Text( + '${value.toInt()} task${value == 1 ? '' : 's'}', + style: const TextStyle( + fontSize: 10, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + ), + ), + interval: 1, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + reservedSize: 30, + getTitlesWidget: (value, meta) => Text( + style: const TextStyle( + fontSize: 10, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + ), + df.format( + DateTime( + firstDate.year, + firstDate.month, + firstDate.day, + ).add(Duration(days: value.round())), + ), + ), + showTitles: true, + interval: 1, + ), + ), + ), + ), + ), + ), + ), + ], + if (_selectedValue == 'daily') ...[ + SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + height: MediaQuery.of(context).size.height * 0.5, + child: PieChart( + PieChartData( + sectionsSpace: 0, + sections: [ + PieChartSectionData( + value: _todayCompleted, // Progress + color: Theme.of(context).colorScheme.primaryFixedDim, + + // Cyan + radius: 60, + title: (_todayPlanned != 0) + // ignore: lines_longer_than_80_chars + ? '${((_todayCompleted / _todayPlanned) * 100).floorToDouble()} %' + : '0 %', + titleStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.secondary, + ), + ), + PieChartSectionData( + titleStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primaryFixedDim, + ), + value: (_todayPlanned > 0 && _todayCompleted > 0) + ? (_todayPlanned - _todayCompleted) + : 1, // + // Total - progress + color: Theme.of(context).colorScheme.secondary, + radius: 60, + showTitle: _todayPlanned == 0 || + (_todayPlanned > 0 && _todayCompleted == 0), + title: '0 %', + ), + ], + ), + ), + ), + ], + SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + child: Column( + children: [ + Text( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + // ignore: lines_longer_than_80_chars + _getCompletedDescription() + _getText(), + ), + Text( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + // ignore: lines_longer_than_80_chars + 'Out of a total goal of ${(_selectedValue == 'weekly') ? (_totalTasksPlanned + _totalTasksCompleted + _totalOverdue).toInt() : _todayPlanned.toInt()}${_getText()}', + ), + Text( + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + _getMissedDescription(), + ), + ], + ), + ), + ], + ); + } + + String _getCompletedDescription() { + if (_selectedValue == 'weekly') { + if (_totalTasksCompleted == 0) { + return '0 tasks completed'; + } else if (_totalTasksCompleted == 1) { + return '1 task completed'; + } else { + return '$_totalTasksCompleted tasks completed'; + } + } else if (_selectedValue == 'daily') { + if (_todayCompleted == 0) { + return '0 tasks completed'; + } else if (_todayCompleted == 1) { + return '1 task completed'; + } else { + return '${_todayCompleted.toInt()} tasks completed'; + } + } + return ''; + } + + String _getMissedDescription() { + if (_selectedValue == 'weekly') { + if (_totalOverdue == 0) { + return 'No tasks were missed'; + } else if (_totalOverdue == 1) { + return '1 task was missed'; + } else { + return '${_totalOverdue.toInt()} tasks were missed'; + } + } else if (_selectedValue == 'daily') { + if (_todayOverdue == 0) { + return 'No tasks were missed'; + } else if (_todayOverdue == 1) { + return '1 task was missed'; + } else { + return '${_todayOverdue.toInt()} tasks were missed'; + } + } + return ''; + } + + int _daysBetween(DateTime from, DateTime to) { + final d1 = DateTime(from.year, from.month, from.day); + final d2 = DateTime(to.year, to.month, to.day); + return (d2.difference(d1).inHours / 24).round(); + } +} diff --git a/app/lib/pages/timer_graph_widget.dart b/app/lib/pages/timer_graph_widget.dart new file mode 100644 index 0000000..a255d93 --- /dev/null +++ b/app/lib/pages/timer_graph_widget.dart @@ -0,0 +1,487 @@ +// ignore_for_file: cascade_invocations + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_controller.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/items_state.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/timer.dart'; +import 'package:intl/intl.dart'; + +class DataPoint { + DataPoint({required this.date, required this.progress}); + DateTime date; + double progress; +} + +class TimerGraphWidget extends ConsumerStatefulWidget { + TimerGraphWidget({super.key}); + + @override + ConsumerState createState() => + _TimerGraphWidgetState(); + + final entries = [ + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + DataPoint(date: DateTime(2024, 7, 7), progress: 0.75), + ]; +} + +class _TimerGraphWidgetState extends ConsumerState { + final gradientColors = [ + Colors.cyan, + Colors.blueAccent, + ]; + final gradient2Colors = [ + Colors.amber, + Colors.amberAccent, + ]; + String _formatTime() { + final minutesTotal = + (_selectedValue == 'weekly') ? _timePlanned : _todayGoal; + final hours = (minutesTotal / 60).floor(); + final minutes = (minutesTotal - (hours * 60)).toInt(); + return '$hours hours : $minutes minutes'; + } + + String _formatCurrent() { + final secondsTotal = + (_selectedValue == 'weekly') ? _timeCompleted : _todayCurrent; + final hours = (secondsTotal / 3600).floor(); + final minutes = ((secondsTotal - (hours * 3600)) / 60).floor(); + return '$hours hours : $minutes minutes'; + } + + void _buildList(List value) { + final items = value; + _timeCompleted = 0; + _timePlanned = 0; + + setState(() { + _todayCurrent = 0; + _todayGoal = 0; + _weeklyPlannedEntries = []; + _weeklyWorkedEntries = []; + for (var i = 0; i <= 6; i++) { + final itemToInsert = DataPoint( + date: _latestDate.subtract(Duration(days: 6 - i)), + progress: 0, + ); + + _weeklyPlannedEntries.add(itemToInsert); + } + + for (var i = 0; i <= 6; i++) { + final itemToInsert = DataPoint( + date: _latestDate.subtract(Duration(days: 6 - i)), + progress: 0, + ); + _weeklyWorkedEntries.add(itemToInsert); + } + }); + for (final item in items) { + if (_selectedValue == 'weekly' && item is TimerItem) { + final parsedDate = DateTime.parse(item.createdOn); + for (var i = 0; i <= 6; i++) { + if (parsedDate.isBefore(_latestDate.subtract(Duration(days: i))) && + parsedDate.isAfter(_latestDate.subtract(Duration(days: i + 1)))) { + logger.i('LOOPING'); + + _timeCompleted += item.current; + _timePlanned += item.goal; + // _maxTasks += 1; + final hours = (item.goal / 60).ceil().toDouble(); + + if (hours > _maxTime) { + _maxTime = hours; + } + + // Update maxAmount + setState(() { + _weeklyPlannedEntries.elementAt(6).progress = item.goal / 60; + _weeklyWorkedEntries.elementAt(6).progress = item.current / 3600; + + // _weeklyWorkedEntries.elementAt(6 - i).progress = + // _tasksCompleted.toDouble(); + }); + } + } + } else if (_selectedValue == 'daily' && item is TimerItem) { + final parsedDate = DateTime.parse(item.createdOn); + + final oneDayAgo = _latestDate.subtract(const Duration(days: 1)); + + if (parsedDate.isBefore(oneDayAgo)) { + logger.i('More than a day old'); + } else if (parsedDate.isAfter(oneDayAgo) && + parsedDate.isBefore(_latestDate)) { + logger.i('TOday'); + _todayCurrent += item.current; + _todayGoal += item.goal; + } + } + } + } + + void _showPreviousWeek() { + if (_selectedValue == 'weekly') { + _latestDate = _latestDate.subtract(const Duration(days: 7)); + } else if (_selectedValue == 'daily') { + logger.i('HMMM'); + _latestDate = _latestDate.subtract(const Duration(days: 1)); + } + final items = ref.watch(itemsProvider); + + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } // get current date + // show past 7 days starting 14 days ago + } + + void _showNextWeek() { + logger.i('CURRENT: ${_todayCurrent / 60}'); + + logger.i('MINUTES TOTAL: $_todayGoal'); + logger.i('PERCENTAGE: ${_todayCurrent / (_todayGoal * 60)}'); + if (_selectedValue == 'weekly') { + _latestDate = _latestDate.add(const Duration(days: 7)); + } else if (_selectedValue == 'daily') { + logger.i('HMMM'); + _latestDate = _latestDate.add(const Duration(days: 1)); + } + if (!_latestDate.isAfter(DateTime.now())) { + final items = ref.watch(itemsProvider); + + ref.watch(homeControllerProvider); + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } // get current date + } + + // show past 7 days starting 14 days ago + } + + String _getText() { + final dateFormat = DateFormat('dd. MMM yyyy'); + + if (_selectedValue == 'weekly' && + _latestDate.isAfter(DateTime.now().subtract(const Duration(days: 6)))) { + return ' this past week'; + } else if (_selectedValue == 'weekly' && + !_latestDate + .isAfter(DateTime.now().subtract(const Duration(days: 6)))) { + return ' ' + 'from' + ' ${dateFormat.format(_latestDate.subtract(const Duration(days: 6)))}' + ' to ${dateFormat.format(_latestDate)}'; + } + final formattedDate = dateFormat.format(_latestDate); + // return ' on ${_latestDate.toString().substring(6, 10)}'; + return ' on $formattedDate'; + } + + List thisWeekItems = []; + List todayItems = []; + String? _selectedValue = 'weekly'; + + int _timeCompleted = 0; + int _timePlanned = 0; + double _maxTime = 0; + DateTime _latestDate = DateTime.now(); + double _todayCurrent = 0; + double _todayGoal = 0; + + List _weeklyPlannedEntries = []; + List _weeklyWorkedEntries = []; + + @override + Widget build(BuildContext context) { + final items = ref.watch(itemsProvider); + + // ignore: unused_element + double getProgress() { + logger.i('CURRENT: $_todayCurrent'); + logger.i('GOAL: $_todayGoal'); + if (_todayGoal == 0) { + return 0; + } + return _todayCurrent / _todayGoal; + } + + ref.watch(homeControllerProvider); + switch (items) { + case AsyncError(:final error): + logger.i('Error: $error'); + case AsyncData(:final value): + final allItems = value; + _buildList(allItems); + default: + logger.i('Hmmm, how can we help?'); + } + + final firstDate = _weeklyPlannedEntries[0].date; + + final flSpots = []; + for (final dataPoint in _weeklyPlannedEntries) { + final xValue = _daysBetween(_weeklyPlannedEntries[0].date, dataPoint.date) + .toDouble(); + final lcbd = FlSpot( + xValue, + dataPoint.progress, + ); + + flSpots.add(lcbd); + } + final weeklyWorkedSpots = []; + for (final dataPoint in _weeklyWorkedEntries) { + final xValue = + _daysBetween(_weeklyWorkedEntries[0].date, dataPoint.date).toDouble(); + final lcbd = FlSpot(xValue, dataPoint.progress); + + weeklyWorkedSpots.add(lcbd); + } + + final df = DateFormat('dd. MMMM'); + + return Column( + children: [ + Container( + height: 75, + color: Theme.of(context).colorScheme.primaryContainer, + margin: const EdgeInsets.only(bottom: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: _showPreviousWeek, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + child: const Text('Previous'), + ), + DropdownButton( + value: _selectedValue, + borderRadius: BorderRadius.circular(10), + onChanged: (value) { + setState(() { + _latestDate = DateTime.now(); + _selectedValue = value; + }); + }, + items: const [ + DropdownMenuItem( + value: 'daily', + child: Text('Daily'), + ), + DropdownMenuItem( + value: 'weekly', + child: Text('Weekly'), + ), + ], + ), + ElevatedButton( + onPressed: _showNextWeek, + style: ButtonStyle( + shape: WidgetStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + child: const Text('Next'), + ), + ], + ), + ), + if (_selectedValue == 'weekly') ...[ + Container( + padding: + const EdgeInsets.only(bottom: 15, top: 15, right: 15, left: 5), + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + height: MediaQuery.of(context).size.height * 0.5, + child: LineChart( + LineChartData( + minY: 0, + maxY: _maxTime, + lineBarsData: [ + LineChartBarData( + isCurved: true, + preventCurveOverShooting: true, + spots: flSpots, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradientColors + .map((color) => color.withOpacity(0.3)) + .toList(), + ), + ), + ), + LineChartBarData( + color: Colors.amber, + isCurved: true, + isStrokeCapRound: true, + preventCurveOverShooting: true, + spots: weeklyWorkedSpots, + belowBarData: BarAreaData( + show: true, + gradient: LinearGradient( + colors: gradient2Colors + .map((color) => color.withOpacity(0.3)) + .toList(), + ), + ), + ), + ], + titlesData: FlTitlesData( + rightTitles: const AxisTitles( + sideTitles: SideTitles( + reservedSize: 30, + interval: 20, + ), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles( + interval: 20, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 50, + getTitlesWidget: (value, meta) => Text( + '$value h', + style: const TextStyle( + fontSize: 10, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + ), + ), + interval: 0.5, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + reservedSize: 30, + getTitlesWidget: (value, meta) => Text( + style: const TextStyle( + fontSize: 10, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.bold, + ), + df.format( + DateTime( + firstDate.year, + firstDate.month, + firstDate.day, + ).add(Duration(days: value.round())), + ), + ), + showTitles: true, + interval: 1, + ), + ), + ), + ), + ), + ), + ), + ], + if (_selectedValue == 'daily') ...[ + SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + height: MediaQuery.of(context).size.height * 0.5, + child: PieChart( + PieChartData( + sectionsSpace: 0, + sections: [ + PieChartSectionData( + value: _todayCurrent / 60, // Progress + color: Theme.of(context).colorScheme.primaryFixedDim, + radius: 60, + title: (_todayGoal > 0) + // ignore: lines_longer_than_80_chars + ? '${((_todayCurrent * 100 / 60) / _todayGoal).round()} %' + : '0 %', + titleStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.secondary, + ), + ), + PieChartSectionData( + value: (_todayCurrent > 0) + ? (_todayGoal - (_todayCurrent / 60)) + : 1, // Total - progress + color: Theme.of(context).colorScheme.secondary, + radius: 60, + showTitle: _todayCurrent <= 0, + titleStyle: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primaryFixedDim, + ), + title: '0%', + ), + ], + ), + ), + ), + ], + SizedBox( + width: MediaQuery.of(context).size.width * 0.90, + child: Column( + children: [ + Text( + 'You worked for ${_formatCurrent()}${_getText()}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Out of a total goal of ${_formatTime()}${_getText()}', + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.onPrimaryContainer, + fontSize: 15, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ); + } + + int _daysBetween(DateTime from, DateTime to) { + final d1 = DateTime(from.year, from.month, from.day); + final d2 = DateTime(to.year, to.month, to.day); + return (d2.difference(d1).inHours / 24).round(); + } +} diff --git a/app/lib/pages/widget_page.dart b/app/lib/pages/widget_page.dart new file mode 100644 index 0000000..af59312 --- /dev/null +++ b/app/lib/pages/widget_page.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; +import 'package:habitrack_app/infrastructure/widget_wall/widget_wall.dart'; + +class WidgetPage extends StatelessWidget { + const WidgetPage({super.key}); + + @override + Widget build(BuildContext context) { + return const WidgetWall(); + } +} diff --git a/app/lib/sembast/global_providers.dart b/app/lib/sembast/global_providers.dart new file mode 100644 index 0000000..9a2e499 --- /dev/null +++ b/app/lib/sembast/global_providers.dart @@ -0,0 +1,5 @@ +import 'package:riverpod/riverpod.dart'; +import 'package:sembast/sembast.dart'; + +final databaseProvider = + Provider((_) => throw Exception('Database not initialized')); diff --git a/app/lib/sembast/hydration.dart b/app/lib/sembast/hydration.dart new file mode 100644 index 0000000..4e01f45 --- /dev/null +++ b/app/lib/sembast/hydration.dart @@ -0,0 +1,31 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'hydration.freezed.dart'; +part 'hydration.g.dart'; + +@freezed +class Hydration with _$Hydration { + const factory Hydration({ + required String widgetType, + required String name, + required int button1Amount, + required int button2Amount, + required int goal, + required int current, + required bool isExpanded, + required bool isVisible, + required String createdOn, + required String completedOn, + @Default(-1) int id, + }) = _Hydration; + + factory Hydration.fromJson(Map json) => + _$HydrationFromJson(json); +} + +extension JsonWithoutId on Hydration { + Map toJsonWithoutId() { + final map = toJson()..remove('id'); + return map; + } +} diff --git a/app/lib/sembast/hydration.freezed.dart b/app/lib/sembast/hydration.freezed.dart new file mode 100644 index 0000000..55602ef --- /dev/null +++ b/app/lib/sembast/hydration.freezed.dart @@ -0,0 +1,392 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'hydration.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +Hydration _$HydrationFromJson(Map json) { + return _Hydration.fromJson(json); +} + +/// @nodoc +mixin _$Hydration { + String get widgetType => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + int get button1Amount => throw _privateConstructorUsedError; + int get button2Amount => throw _privateConstructorUsedError; + int get goal => throw _privateConstructorUsedError; + int get current => throw _privateConstructorUsedError; + bool get isExpanded => throw _privateConstructorUsedError; + bool get isVisible => throw _privateConstructorUsedError; + String get createdOn => throw _privateConstructorUsedError; + String get completedOn => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $HydrationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $HydrationCopyWith<$Res> { + factory $HydrationCopyWith(Hydration value, $Res Function(Hydration) then) = + _$HydrationCopyWithImpl<$Res, Hydration>; + @useResult + $Res call( + {String widgetType, + String name, + int button1Amount, + int button2Amount, + int goal, + int current, + bool isExpanded, + bool isVisible, + String createdOn, + String completedOn, + int id}); +} + +/// @nodoc +class _$HydrationCopyWithImpl<$Res, $Val extends Hydration> + implements $HydrationCopyWith<$Res> { + _$HydrationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? widgetType = null, + Object? name = null, + Object? button1Amount = null, + Object? button2Amount = null, + Object? goal = null, + Object? current = null, + Object? isExpanded = null, + Object? isVisible = null, + Object? createdOn = null, + Object? completedOn = null, + Object? id = null, + }) { + return _then(_value.copyWith( + widgetType: null == widgetType + ? _value.widgetType + : widgetType // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + button1Amount: null == button1Amount + ? _value.button1Amount + : button1Amount // ignore: cast_nullable_to_non_nullable + as int, + button2Amount: null == button2Amount + ? _value.button2Amount + : button2Amount // ignore: cast_nullable_to_non_nullable + as int, + goal: null == goal + ? _value.goal + : goal // ignore: cast_nullable_to_non_nullable + as int, + current: null == current + ? _value.current + : current // ignore: cast_nullable_to_non_nullable + as int, + isExpanded: null == isExpanded + ? _value.isExpanded + : isExpanded // ignore: cast_nullable_to_non_nullable + as bool, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + createdOn: null == createdOn + ? _value.createdOn + : createdOn // ignore: cast_nullable_to_non_nullable + as String, + completedOn: null == completedOn + ? _value.completedOn + : completedOn // ignore: cast_nullable_to_non_nullable + as String, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$HydrationImplCopyWith<$Res> + implements $HydrationCopyWith<$Res> { + factory _$$HydrationImplCopyWith( + _$HydrationImpl value, $Res Function(_$HydrationImpl) then) = + __$$HydrationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String widgetType, + String name, + int button1Amount, + int button2Amount, + int goal, + int current, + bool isExpanded, + bool isVisible, + String createdOn, + String completedOn, + int id}); +} + +/// @nodoc +class __$$HydrationImplCopyWithImpl<$Res> + extends _$HydrationCopyWithImpl<$Res, _$HydrationImpl> + implements _$$HydrationImplCopyWith<$Res> { + __$$HydrationImplCopyWithImpl( + _$HydrationImpl _value, $Res Function(_$HydrationImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? widgetType = null, + Object? name = null, + Object? button1Amount = null, + Object? button2Amount = null, + Object? goal = null, + Object? current = null, + Object? isExpanded = null, + Object? isVisible = null, + Object? createdOn = null, + Object? completedOn = null, + Object? id = null, + }) { + return _then(_$HydrationImpl( + widgetType: null == widgetType + ? _value.widgetType + : widgetType // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + button1Amount: null == button1Amount + ? _value.button1Amount + : button1Amount // ignore: cast_nullable_to_non_nullable + as int, + button2Amount: null == button2Amount + ? _value.button2Amount + : button2Amount // ignore: cast_nullable_to_non_nullable + as int, + goal: null == goal + ? _value.goal + : goal // ignore: cast_nullable_to_non_nullable + as int, + current: null == current + ? _value.current + : current // ignore: cast_nullable_to_non_nullable + as int, + isExpanded: null == isExpanded + ? _value.isExpanded + : isExpanded // ignore: cast_nullable_to_non_nullable + as bool, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + createdOn: null == createdOn + ? _value.createdOn + : createdOn // ignore: cast_nullable_to_non_nullable + as String, + completedOn: null == completedOn + ? _value.completedOn + : completedOn // ignore: cast_nullable_to_non_nullable + as String, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$HydrationImpl with DiagnosticableTreeMixin implements _Hydration { + const _$HydrationImpl( + {required this.widgetType, + required this.name, + required this.button1Amount, + required this.button2Amount, + required this.goal, + required this.current, + required this.isExpanded, + required this.isVisible, + required this.createdOn, + required this.completedOn, + this.id = -1}); + + factory _$HydrationImpl.fromJson(Map json) => + _$$HydrationImplFromJson(json); + + @override + final String widgetType; + @override + final String name; + @override + final int button1Amount; + @override + final int button2Amount; + @override + final int goal; + @override + final int current; + @override + final bool isExpanded; + @override + final bool isVisible; + @override + final String createdOn; + @override + final String completedOn; + @override + @JsonKey() + final int id; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'Hydration(widgetType: $widgetType, name: $name, button1Amount: $button1Amount, button2Amount: $button2Amount, goal: $goal, current: $current, isExpanded: $isExpanded, isVisible: $isVisible, createdOn: $createdOn, completedOn: $completedOn, id: $id)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'Hydration')) + ..add(DiagnosticsProperty('widgetType', widgetType)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('button1Amount', button1Amount)) + ..add(DiagnosticsProperty('button2Amount', button2Amount)) + ..add(DiagnosticsProperty('goal', goal)) + ..add(DiagnosticsProperty('current', current)) + ..add(DiagnosticsProperty('isExpanded', isExpanded)) + ..add(DiagnosticsProperty('isVisible', isVisible)) + ..add(DiagnosticsProperty('createdOn', createdOn)) + ..add(DiagnosticsProperty('completedOn', completedOn)) + ..add(DiagnosticsProperty('id', id)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$HydrationImpl && + (identical(other.widgetType, widgetType) || + other.widgetType == widgetType) && + (identical(other.name, name) || other.name == name) && + (identical(other.button1Amount, button1Amount) || + other.button1Amount == button1Amount) && + (identical(other.button2Amount, button2Amount) || + other.button2Amount == button2Amount) && + (identical(other.goal, goal) || other.goal == goal) && + (identical(other.current, current) || other.current == current) && + (identical(other.isExpanded, isExpanded) || + other.isExpanded == isExpanded) && + (identical(other.isVisible, isVisible) || + other.isVisible == isVisible) && + (identical(other.createdOn, createdOn) || + other.createdOn == createdOn) && + (identical(other.completedOn, completedOn) || + other.completedOn == completedOn) && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + widgetType, + name, + button1Amount, + button2Amount, + goal, + current, + isExpanded, + isVisible, + createdOn, + completedOn, + id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$HydrationImplCopyWith<_$HydrationImpl> get copyWith => + __$$HydrationImplCopyWithImpl<_$HydrationImpl>(this, _$identity); + + @override + Map toJson() { + return _$$HydrationImplToJson( + this, + ); + } +} + +abstract class _Hydration implements Hydration { + const factory _Hydration( + {required final String widgetType, + required final String name, + required final int button1Amount, + required final int button2Amount, + required final int goal, + required final int current, + required final bool isExpanded, + required final bool isVisible, + required final String createdOn, + required final String completedOn, + final int id}) = _$HydrationImpl; + + factory _Hydration.fromJson(Map json) = + _$HydrationImpl.fromJson; + + @override + String get widgetType; + @override + String get name; + @override + int get button1Amount; + @override + int get button2Amount; + @override + int get goal; + @override + int get current; + @override + bool get isExpanded; + @override + bool get isVisible; + @override + String get createdOn; + @override + String get completedOn; + @override + int get id; + @override + @JsonKey(ignore: true) + _$$HydrationImplCopyWith<_$HydrationImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/app/lib/sembast/hydration.g.dart b/app/lib/sembast/hydration.g.dart new file mode 100644 index 0000000..dfb3e23 --- /dev/null +++ b/app/lib/sembast/hydration.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'hydration.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$HydrationImpl _$$HydrationImplFromJson(Map json) => + _$HydrationImpl( + widgetType: json['widgetType'] as String, + name: json['name'] as String, + button1Amount: (json['button1Amount'] as num).toInt(), + button2Amount: (json['button2Amount'] as num).toInt(), + goal: (json['goal'] as num).toInt(), + current: (json['current'] as num).toInt(), + isExpanded: json['isExpanded'] as bool, + isVisible: json['isVisible'] as bool, + createdOn: json['createdOn'] as String, + completedOn: json['completedOn'] as String, + id: (json['id'] as num?)?.toInt() ?? -1, + ); + +Map _$$HydrationImplToJson(_$HydrationImpl instance) => + { + 'widgetType': instance.widgetType, + 'name': instance.name, + 'button1Amount': instance.button1Amount, + 'button2Amount': instance.button2Amount, + 'goal': instance.goal, + 'current': instance.current, + 'isExpanded': instance.isExpanded, + 'isVisible': instance.isVisible, + 'createdOn': instance.createdOn, + 'completedOn': instance.completedOn, + 'id': instance.id, + }; diff --git a/app/lib/sembast/item_repository.dart b/app/lib/sembast/item_repository.dart new file mode 100644 index 0000000..725d5b4 --- /dev/null +++ b/app/lib/sembast/item_repository.dart @@ -0,0 +1,9 @@ +abstract class ItemRepository { + Future insertItem(dynamic item); + + Future updateItem(dynamic item); + + Future deleteItem(int itemId); + + Stream> getAllItemsStream(); +} diff --git a/app/lib/sembast/sembast_item_repository.dart b/app/lib/sembast/sembast_item_repository.dart new file mode 100644 index 0000000..8d651fb --- /dev/null +++ b/app/lib/sembast/sembast_item_repository.dart @@ -0,0 +1,71 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:habitrack_app/main.dart'; +import 'package:habitrack_app/sembast/global_providers.dart'; +import 'package:habitrack_app/sembast/hydration.dart'; +import 'package:habitrack_app/sembast/item_repository.dart'; +import 'package:habitrack_app/sembast/tasks_list.dart'; +import 'package:habitrack_app/sembast/timer.dart'; +import 'package:sembast/sembast.dart'; + +final itemRepositoryProvider = Provider( + (ref) => SembastItemRepository( + database: ref.watch(databaseProvider), + ), +); + +class SembastItemRepository implements ItemRepository { + SembastItemRepository({required this.database}) { + _store = intMapStoreFactory.store('item_store'); + } + final Database database; + late final StoreRef> _store; + + @override + Future insertItem(dynamic item) { + if (item is Hydration) { + _store.add(database, item.toJson()); + } else if (item is TimerItem) { + _store.add(database, item.toJson()); + } else if (item is TasksItem) { + _store.add(database, item.toJson()); + } + return Future.value(0); + } + + @override + Future updateItem(dynamic item) { + if (item is Hydration) { + _store.record(item.id).update(database, item.toJson()); + } else if (item is TimerItem) { + _store.record(item.id).update(database, item.toJson()); + } else if (item is TasksItem) { + logger.i('UPDATING TASKS ITEM'); + _store.record(item.id).update(database, item.toJson()); + } + logger.i('Item got past update: $item'); + return Future.value(); + // throw Error(); + } + + @override + Future deleteItem(int itemId) => _store.record(itemId).delete(database); + + @override + Stream> getAllItemsStream() => + _store.query().onSnapshots(database).map( + (snapshot) => snapshot.map((item) { + logger.i('Querying the database! ${item.value}'); + if (item.value.toString().contains('widgetType: Hydration')) { + logger.i('Le hydration has arrived'); + + return Hydration.fromJson(item.value).copyWith(id: item.key); + } else if (item.value.toString().contains('widgetType: Timer')) { + logger.i('Le timer has arrived'); + return TimerItem.fromJson(item.value).copyWith(id: item.key); + } else if (item.value.toString().contains('widgetType: TODO')) { + logger.i('Le TODO has arrived'); + return TasksItem.fromJson(item.value).copyWith(id: item.key); + } + }).toList(growable: false), + ); +} diff --git a/app/lib/sembast/tasks_list.dart b/app/lib/sembast/tasks_list.dart new file mode 100644 index 0000000..10cf4f5 --- /dev/null +++ b/app/lib/sembast/tasks_list.dart @@ -0,0 +1,53 @@ +// ignore_for_file: flutter_style_todos + +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'tasks_list.freezed.dart'; +part 'tasks_list.g.dart'; + +@freezed +class IndividualTodo with _$IndividualTodo { + const factory IndividualTodo({ + required String todo, + required bool isCompleted, + required DateTime dueDate, + }) = _IndividualTodo; + factory IndividualTodo.fromJson(Map json) => + _$IndividualTodoFromJson(json); +} + +@freezed +class TasksItem with _$TasksItem { + const factory TasksItem({ + required String widgetType, + required String name, + required bool isExpanded, + required List taskList, + required List completedTaskList, + required bool isVisible, + @Default(-1) int id, + }) = _TasksItem; + + factory TasksItem.fromJson(Map json) => + _$TasksItemFromJson(json); +} + +extension JsonWithoutId on TasksItem { + Map toJsonWithoutId() { + final map = toJson()..remove('id'); + // map['taskList'] = taskList.map((todo) => todo.toJson()).toList(); + //map['completedTaskList'] = + // completedTaskList.map((todo) => todo.toJson()).toList(); + return map; + } +} +/* +extension JsonWithoutID on TODO { + Map toJsonWithoutId() { + final map = toJson()..remove('id'); + map['taskList'] = taskList.map((todo) => todo.toJson()).toList(); + map['completedTaskList'] = + completedTaskList.map((todo) => todo.toJson()).toList(); + return map; + } +}*/ diff --git a/app/lib/sembast/tasks_list.freezed.dart b/app/lib/sembast/tasks_list.freezed.dart new file mode 100644 index 0000000..489b46e --- /dev/null +++ b/app/lib/sembast/tasks_list.freezed.dart @@ -0,0 +1,502 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'tasks_list.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +IndividualTodo _$IndividualTodoFromJson(Map json) { + return _IndividualTodo.fromJson(json); +} + +/// @nodoc +mixin _$IndividualTodo { + String get todo => throw _privateConstructorUsedError; + bool get isCompleted => throw _privateConstructorUsedError; + DateTime get dueDate => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $IndividualTodoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $IndividualTodoCopyWith<$Res> { + factory $IndividualTodoCopyWith( + IndividualTodo value, $Res Function(IndividualTodo) then) = + _$IndividualTodoCopyWithImpl<$Res, IndividualTodo>; + @useResult + $Res call({String todo, bool isCompleted, DateTime dueDate}); +} + +/// @nodoc +class _$IndividualTodoCopyWithImpl<$Res, $Val extends IndividualTodo> + implements $IndividualTodoCopyWith<$Res> { + _$IndividualTodoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? todo = null, + Object? isCompleted = null, + Object? dueDate = null, + }) { + return _then(_value.copyWith( + todo: null == todo + ? _value.todo + : todo // ignore: cast_nullable_to_non_nullable + as String, + isCompleted: null == isCompleted + ? _value.isCompleted + : isCompleted // ignore: cast_nullable_to_non_nullable + as bool, + dueDate: null == dueDate + ? _value.dueDate + : dueDate // ignore: cast_nullable_to_non_nullable + as DateTime, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$IndividualTodoImplCopyWith<$Res> + implements $IndividualTodoCopyWith<$Res> { + factory _$$IndividualTodoImplCopyWith(_$IndividualTodoImpl value, + $Res Function(_$IndividualTodoImpl) then) = + __$$IndividualTodoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String todo, bool isCompleted, DateTime dueDate}); +} + +/// @nodoc +class __$$IndividualTodoImplCopyWithImpl<$Res> + extends _$IndividualTodoCopyWithImpl<$Res, _$IndividualTodoImpl> + implements _$$IndividualTodoImplCopyWith<$Res> { + __$$IndividualTodoImplCopyWithImpl( + _$IndividualTodoImpl _value, $Res Function(_$IndividualTodoImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? todo = null, + Object? isCompleted = null, + Object? dueDate = null, + }) { + return _then(_$IndividualTodoImpl( + todo: null == todo + ? _value.todo + : todo // ignore: cast_nullable_to_non_nullable + as String, + isCompleted: null == isCompleted + ? _value.isCompleted + : isCompleted // ignore: cast_nullable_to_non_nullable + as bool, + dueDate: null == dueDate + ? _value.dueDate + : dueDate // ignore: cast_nullable_to_non_nullable + as DateTime, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$IndividualTodoImpl + with DiagnosticableTreeMixin + implements _IndividualTodo { + const _$IndividualTodoImpl( + {required this.todo, required this.isCompleted, required this.dueDate}); + + factory _$IndividualTodoImpl.fromJson(Map json) => + _$$IndividualTodoImplFromJson(json); + + @override + final String todo; + @override + final bool isCompleted; + @override + final DateTime dueDate; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'IndividualTodo(todo: $todo, isCompleted: $isCompleted, dueDate: $dueDate)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'IndividualTodo')) + ..add(DiagnosticsProperty('todo', todo)) + ..add(DiagnosticsProperty('isCompleted', isCompleted)) + ..add(DiagnosticsProperty('dueDate', dueDate)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$IndividualTodoImpl && + (identical(other.todo, todo) || other.todo == todo) && + (identical(other.isCompleted, isCompleted) || + other.isCompleted == isCompleted) && + (identical(other.dueDate, dueDate) || other.dueDate == dueDate)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, todo, isCompleted, dueDate); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$IndividualTodoImplCopyWith<_$IndividualTodoImpl> get copyWith => + __$$IndividualTodoImplCopyWithImpl<_$IndividualTodoImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$IndividualTodoImplToJson( + this, + ); + } +} + +abstract class _IndividualTodo implements IndividualTodo { + const factory _IndividualTodo( + {required final String todo, + required final bool isCompleted, + required final DateTime dueDate}) = _$IndividualTodoImpl; + + factory _IndividualTodo.fromJson(Map json) = + _$IndividualTodoImpl.fromJson; + + @override + String get todo; + @override + bool get isCompleted; + @override + DateTime get dueDate; + @override + @JsonKey(ignore: true) + _$$IndividualTodoImplCopyWith<_$IndividualTodoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +TasksItem _$TasksItemFromJson(Map json) { + return _TasksItem.fromJson(json); +} + +/// @nodoc +mixin _$TasksItem { + String get widgetType => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + bool get isExpanded => throw _privateConstructorUsedError; + List get taskList => throw _privateConstructorUsedError; + List get completedTaskList => throw _privateConstructorUsedError; + bool get isVisible => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TasksItemCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TasksItemCopyWith<$Res> { + factory $TasksItemCopyWith(TasksItem value, $Res Function(TasksItem) then) = + _$TasksItemCopyWithImpl<$Res, TasksItem>; + @useResult + $Res call( + {String widgetType, + String name, + bool isExpanded, + List taskList, + List completedTaskList, + bool isVisible, + int id}); +} + +/// @nodoc +class _$TasksItemCopyWithImpl<$Res, $Val extends TasksItem> + implements $TasksItemCopyWith<$Res> { + _$TasksItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? widgetType = null, + Object? name = null, + Object? isExpanded = null, + Object? taskList = null, + Object? completedTaskList = null, + Object? isVisible = null, + Object? id = null, + }) { + return _then(_value.copyWith( + widgetType: null == widgetType + ? _value.widgetType + : widgetType // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + isExpanded: null == isExpanded + ? _value.isExpanded + : isExpanded // ignore: cast_nullable_to_non_nullable + as bool, + taskList: null == taskList + ? _value.taskList + : taskList // ignore: cast_nullable_to_non_nullable + as List, + completedTaskList: null == completedTaskList + ? _value.completedTaskList + : completedTaskList // ignore: cast_nullable_to_non_nullable + as List, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TasksItemImplCopyWith<$Res> + implements $TasksItemCopyWith<$Res> { + factory _$$TasksItemImplCopyWith( + _$TasksItemImpl value, $Res Function(_$TasksItemImpl) then) = + __$$TasksItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String widgetType, + String name, + bool isExpanded, + List taskList, + List completedTaskList, + bool isVisible, + int id}); +} + +/// @nodoc +class __$$TasksItemImplCopyWithImpl<$Res> + extends _$TasksItemCopyWithImpl<$Res, _$TasksItemImpl> + implements _$$TasksItemImplCopyWith<$Res> { + __$$TasksItemImplCopyWithImpl( + _$TasksItemImpl _value, $Res Function(_$TasksItemImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? widgetType = null, + Object? name = null, + Object? isExpanded = null, + Object? taskList = null, + Object? completedTaskList = null, + Object? isVisible = null, + Object? id = null, + }) { + return _then(_$TasksItemImpl( + widgetType: null == widgetType + ? _value.widgetType + : widgetType // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + isExpanded: null == isExpanded + ? _value.isExpanded + : isExpanded // ignore: cast_nullable_to_non_nullable + as bool, + taskList: null == taskList + ? _value._taskList + : taskList // ignore: cast_nullable_to_non_nullable + as List, + completedTaskList: null == completedTaskList + ? _value._completedTaskList + : completedTaskList // ignore: cast_nullable_to_non_nullable + as List, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TasksItemImpl with DiagnosticableTreeMixin implements _TasksItem { + const _$TasksItemImpl( + {required this.widgetType, + required this.name, + required this.isExpanded, + required final List taskList, + required final List completedTaskList, + required this.isVisible, + this.id = -1}) + : _taskList = taskList, + _completedTaskList = completedTaskList; + + factory _$TasksItemImpl.fromJson(Map json) => + _$$TasksItemImplFromJson(json); + + @override + final String widgetType; + @override + final String name; + @override + final bool isExpanded; + final List _taskList; + @override + List get taskList { + if (_taskList is EqualUnmodifiableListView) return _taskList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_taskList); + } + + final List _completedTaskList; + @override + List get completedTaskList { + if (_completedTaskList is EqualUnmodifiableListView) + return _completedTaskList; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_completedTaskList); + } + + @override + final bool isVisible; + @override + @JsonKey() + final int id; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TasksItem(widgetType: $widgetType, name: $name, isExpanded: $isExpanded, taskList: $taskList, completedTaskList: $completedTaskList, isVisible: $isVisible, id: $id)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TasksItem')) + ..add(DiagnosticsProperty('widgetType', widgetType)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('isExpanded', isExpanded)) + ..add(DiagnosticsProperty('taskList', taskList)) + ..add(DiagnosticsProperty('completedTaskList', completedTaskList)) + ..add(DiagnosticsProperty('isVisible', isVisible)) + ..add(DiagnosticsProperty('id', id)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TasksItemImpl && + (identical(other.widgetType, widgetType) || + other.widgetType == widgetType) && + (identical(other.name, name) || other.name == name) && + (identical(other.isExpanded, isExpanded) || + other.isExpanded == isExpanded) && + const DeepCollectionEquality().equals(other._taskList, _taskList) && + const DeepCollectionEquality() + .equals(other._completedTaskList, _completedTaskList) && + (identical(other.isVisible, isVisible) || + other.isVisible == isVisible) && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash( + runtimeType, + widgetType, + name, + isExpanded, + const DeepCollectionEquality().hash(_taskList), + const DeepCollectionEquality().hash(_completedTaskList), + isVisible, + id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TasksItemImplCopyWith<_$TasksItemImpl> get copyWith => + __$$TasksItemImplCopyWithImpl<_$TasksItemImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TasksItemImplToJson( + this, + ); + } +} + +abstract class _TasksItem implements TasksItem { + const factory _TasksItem( + {required final String widgetType, + required final String name, + required final bool isExpanded, + required final List taskList, + required final List completedTaskList, + required final bool isVisible, + final int id}) = _$TasksItemImpl; + + factory _TasksItem.fromJson(Map json) = + _$TasksItemImpl.fromJson; + + @override + String get widgetType; + @override + String get name; + @override + bool get isExpanded; + @override + List get taskList; + @override + List get completedTaskList; + @override + bool get isVisible; + @override + int get id; + @override + @JsonKey(ignore: true) + _$$TasksItemImplCopyWith<_$TasksItemImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/app/lib/sembast/tasks_list.g.dart b/app/lib/sembast/tasks_list.g.dart new file mode 100644 index 0000000..362e934 --- /dev/null +++ b/app/lib/sembast/tasks_list.g.dart @@ -0,0 +1,47 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'tasks_list.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$IndividualTodoImpl _$$IndividualTodoImplFromJson(Map json) => + _$IndividualTodoImpl( + todo: json['todo'] as String, + isCompleted: json['isCompleted'] as bool, + dueDate: DateTime.parse(json['dueDate'] as String), + ); + +Map _$$IndividualTodoImplToJson( + _$IndividualTodoImpl instance) => + { + 'todo': instance.todo, + 'isCompleted': instance.isCompleted, + 'dueDate': instance.dueDate.toIso8601String(), + }; + +_$TasksItemImpl _$$TasksItemImplFromJson(Map json) => + _$TasksItemImpl( + widgetType: json['widgetType'] as String, + name: json['name'] as String, + isExpanded: json['isExpanded'] as bool, + taskList: + (json['taskList'] as List).map((e) => e as String).toList(), + completedTaskList: (json['completedTaskList'] as List) + .map((e) => e as String) + .toList(), + isVisible: json['isVisible'] as bool, + id: (json['id'] as num?)?.toInt() ?? -1, + ); + +Map _$$TasksItemImplToJson(_$TasksItemImpl instance) => + { + 'widgetType': instance.widgetType, + 'name': instance.name, + 'isExpanded': instance.isExpanded, + 'taskList': instance.taskList, + 'completedTaskList': instance.completedTaskList, + 'isVisible': instance.isVisible, + 'id': instance.id, + }; diff --git a/app/lib/sembast/timer.dart b/app/lib/sembast/timer.dart new file mode 100644 index 0000000..c8a8578 --- /dev/null +++ b/app/lib/sembast/timer.dart @@ -0,0 +1,50 @@ +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'timer.freezed.dart'; +part 'timer.g.dart'; + +@freezed +class TimerItem with _$TimerItem { + const factory TimerItem({ + required String widgetType, + required String name, + required int current, + required int goal, + required bool isExpanded, + required bool isVisible, + required String createdOn, + required String completedOn, + required String state, + @Default(-1) int id, + }) = _TimerItem; + + factory TimerItem.fromJson(Map json) => + _$TimerItemFromJson(json); +} + +extension JsonWithoutId on TimerItem { + Map toJsonWithoutId() { + final map = toJson()..remove('id'); + return map; + } +} + +/* +@freezed +class Student with _$Student { + const factory Student({ + required String name, + int? id, + }) = _Student; + + factory Student.fromJson(Map json) => + _$StudentFromJson(json); +} + +extension JsonWithoutId on Student { + Map toJsonWithoutId() { + final map = toJson()..remove('id'); + return map; + } +} +*/ diff --git a/app/lib/sembast/timer.freezed.dart b/app/lib/sembast/timer.freezed.dart new file mode 100644 index 0000000..ac6bf0a --- /dev/null +++ b/app/lib/sembast/timer.freezed.dart @@ -0,0 +1,358 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'timer.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +TimerItem _$TimerItemFromJson(Map json) { + return _TimerItem.fromJson(json); +} + +/// @nodoc +mixin _$TimerItem { + String get widgetType => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + int get current => throw _privateConstructorUsedError; + int get goal => throw _privateConstructorUsedError; + bool get isExpanded => throw _privateConstructorUsedError; + bool get isVisible => throw _privateConstructorUsedError; + String get createdOn => throw _privateConstructorUsedError; + String get completedOn => throw _privateConstructorUsedError; + String get state => throw _privateConstructorUsedError; + int get id => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $TimerItemCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TimerItemCopyWith<$Res> { + factory $TimerItemCopyWith(TimerItem value, $Res Function(TimerItem) then) = + _$TimerItemCopyWithImpl<$Res, TimerItem>; + @useResult + $Res call( + {String widgetType, + String name, + int current, + int goal, + bool isExpanded, + bool isVisible, + String createdOn, + String completedOn, + String state, + int id}); +} + +/// @nodoc +class _$TimerItemCopyWithImpl<$Res, $Val extends TimerItem> + implements $TimerItemCopyWith<$Res> { + _$TimerItemCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? widgetType = null, + Object? name = null, + Object? current = null, + Object? goal = null, + Object? isExpanded = null, + Object? isVisible = null, + Object? createdOn = null, + Object? completedOn = null, + Object? state = null, + Object? id = null, + }) { + return _then(_value.copyWith( + widgetType: null == widgetType + ? _value.widgetType + : widgetType // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + current: null == current + ? _value.current + : current // ignore: cast_nullable_to_non_nullable + as int, + goal: null == goal + ? _value.goal + : goal // ignore: cast_nullable_to_non_nullable + as int, + isExpanded: null == isExpanded + ? _value.isExpanded + : isExpanded // ignore: cast_nullable_to_non_nullable + as bool, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + createdOn: null == createdOn + ? _value.createdOn + : createdOn // ignore: cast_nullable_to_non_nullable + as String, + completedOn: null == completedOn + ? _value.completedOn + : completedOn // ignore: cast_nullable_to_non_nullable + as String, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TimerItemImplCopyWith<$Res> + implements $TimerItemCopyWith<$Res> { + factory _$$TimerItemImplCopyWith( + _$TimerItemImpl value, $Res Function(_$TimerItemImpl) then) = + __$$TimerItemImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String widgetType, + String name, + int current, + int goal, + bool isExpanded, + bool isVisible, + String createdOn, + String completedOn, + String state, + int id}); +} + +/// @nodoc +class __$$TimerItemImplCopyWithImpl<$Res> + extends _$TimerItemCopyWithImpl<$Res, _$TimerItemImpl> + implements _$$TimerItemImplCopyWith<$Res> { + __$$TimerItemImplCopyWithImpl( + _$TimerItemImpl _value, $Res Function(_$TimerItemImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? widgetType = null, + Object? name = null, + Object? current = null, + Object? goal = null, + Object? isExpanded = null, + Object? isVisible = null, + Object? createdOn = null, + Object? completedOn = null, + Object? state = null, + Object? id = null, + }) { + return _then(_$TimerItemImpl( + widgetType: null == widgetType + ? _value.widgetType + : widgetType // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + current: null == current + ? _value.current + : current // ignore: cast_nullable_to_non_nullable + as int, + goal: null == goal + ? _value.goal + : goal // ignore: cast_nullable_to_non_nullable + as int, + isExpanded: null == isExpanded + ? _value.isExpanded + : isExpanded // ignore: cast_nullable_to_non_nullable + as bool, + isVisible: null == isVisible + ? _value.isVisible + : isVisible // ignore: cast_nullable_to_non_nullable + as bool, + createdOn: null == createdOn + ? _value.createdOn + : createdOn // ignore: cast_nullable_to_non_nullable + as String, + completedOn: null == completedOn + ? _value.completedOn + : completedOn // ignore: cast_nullable_to_non_nullable + as String, + state: null == state + ? _value.state + : state // ignore: cast_nullable_to_non_nullable + as String, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TimerItemImpl with DiagnosticableTreeMixin implements _TimerItem { + const _$TimerItemImpl( + {required this.widgetType, + required this.name, + required this.current, + required this.goal, + required this.isExpanded, + required this.isVisible, + required this.createdOn, + required this.completedOn, + required this.state, + this.id = -1}); + + factory _$TimerItemImpl.fromJson(Map json) => + _$$TimerItemImplFromJson(json); + + @override + final String widgetType; + @override + final String name; + @override + final int current; + @override + final int goal; + @override + final bool isExpanded; + @override + final bool isVisible; + @override + final String createdOn; + @override + final String completedOn; + @override + final String state; + @override + @JsonKey() + final int id; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'TimerItem(widgetType: $widgetType, name: $name, current: $current, goal: $goal, isExpanded: $isExpanded, isVisible: $isVisible, createdOn: $createdOn, completedOn: $completedOn, state: $state, id: $id)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'TimerItem')) + ..add(DiagnosticsProperty('widgetType', widgetType)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('current', current)) + ..add(DiagnosticsProperty('goal', goal)) + ..add(DiagnosticsProperty('isExpanded', isExpanded)) + ..add(DiagnosticsProperty('isVisible', isVisible)) + ..add(DiagnosticsProperty('createdOn', createdOn)) + ..add(DiagnosticsProperty('completedOn', completedOn)) + ..add(DiagnosticsProperty('state', state)) + ..add(DiagnosticsProperty('id', id)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TimerItemImpl && + (identical(other.widgetType, widgetType) || + other.widgetType == widgetType) && + (identical(other.name, name) || other.name == name) && + (identical(other.current, current) || other.current == current) && + (identical(other.goal, goal) || other.goal == goal) && + (identical(other.isExpanded, isExpanded) || + other.isExpanded == isExpanded) && + (identical(other.isVisible, isVisible) || + other.isVisible == isVisible) && + (identical(other.createdOn, createdOn) || + other.createdOn == createdOn) && + (identical(other.completedOn, completedOn) || + other.completedOn == completedOn) && + (identical(other.state, state) || other.state == state) && + (identical(other.id, id) || other.id == id)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, widgetType, name, current, goal, + isExpanded, isVisible, createdOn, completedOn, state, id); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$TimerItemImplCopyWith<_$TimerItemImpl> get copyWith => + __$$TimerItemImplCopyWithImpl<_$TimerItemImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TimerItemImplToJson( + this, + ); + } +} + +abstract class _TimerItem implements TimerItem { + const factory _TimerItem( + {required final String widgetType, + required final String name, + required final int current, + required final int goal, + required final bool isExpanded, + required final bool isVisible, + required final String createdOn, + required final String completedOn, + required final String state, + final int id}) = _$TimerItemImpl; + + factory _TimerItem.fromJson(Map json) = + _$TimerItemImpl.fromJson; + + @override + String get widgetType; + @override + String get name; + @override + int get current; + @override + int get goal; + @override + bool get isExpanded; + @override + bool get isVisible; + @override + String get createdOn; + @override + String get completedOn; + @override + String get state; + @override + int get id; + @override + @JsonKey(ignore: true) + _$$TimerItemImplCopyWith<_$TimerItemImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/app/lib/sembast/timer.g.dart b/app/lib/sembast/timer.g.dart new file mode 100644 index 0000000..99e0b60 --- /dev/null +++ b/app/lib/sembast/timer.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'timer.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$TimerItemImpl _$$TimerItemImplFromJson(Map json) => + _$TimerItemImpl( + widgetType: json['widgetType'] as String, + name: json['name'] as String, + current: (json['current'] as num).toInt(), + goal: (json['goal'] as num).toInt(), + isExpanded: json['isExpanded'] as bool, + isVisible: json['isVisible'] as bool, + createdOn: json['createdOn'] as String, + completedOn: json['completedOn'] as String, + state: json['state'] as String, + id: (json['id'] as num?)?.toInt() ?? -1, + ); + +Map _$$TimerItemImplToJson(_$TimerItemImpl instance) => + { + 'widgetType': instance.widgetType, + 'name': instance.name, + 'current': instance.current, + 'goal': instance.goal, + 'isExpanded': instance.isExpanded, + 'isVisible': instance.isVisible, + 'createdOn': instance.createdOn, + 'completedOn': instance.completedOn, + 'state': instance.state, + 'id': instance.id, + }; diff --git a/app/linux/.gitignore b/app/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/app/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/app/linux/CMakeLists.txt b/app/linux/CMakeLists.txt new file mode 100644 index 0000000..dc6c62b --- /dev/null +++ b/app/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "habitrack_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.habitrack_app") + +# 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) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/app/linux/flutter/CMakeLists.txt b/app/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/app/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/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..b898c8c --- /dev/null +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin"); + isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar); +} diff --git a/app/linux/flutter/generated_plugin_registrant.h b/app/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/app/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/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..cb083af --- /dev/null +++ b/app/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + isar_flutter_libs +) + +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/app/linux/main.cc b/app/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/app/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/app/linux/my_application.cc b/app/linux/my_application.cc new file mode 100644 index 0000000..547af73 --- /dev/null +++ b/app/linux/my_application.cc @@ -0,0 +1,124 @@ +#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) + +// 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, "habitrack_app"); + 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, "habitrack_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements 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() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/app/linux/my_application.h b/app/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/app/linux/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/app/macos/.gitignore b/app/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/app/macos/Flutter/Flutter-Debug.xcconfig b/app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/app/macos/Flutter/Flutter-Release.xcconfig b/app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..2a3b1d0 --- /dev/null +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,16 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import isar_flutter_libs +import path_provider_foundation +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/app/macos/Runner.xcodeproj/project.pbxproj b/app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..bfd4e9a --- /dev/null +++ b/app/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 /* habitrack_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "habitrack_app.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 /* habitrack_app.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 /* habitrack_app.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.habitrackApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/habitrack_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/habitrack_app"; + }; + 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.habitrackApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/habitrack_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/habitrack_app"; + }; + 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.habitrackApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/habitrack_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/habitrack_app"; + }; + 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.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..4b9aade --- /dev/null +++ b/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/macos/Runner.xcworkspace/contents.xcworkspacedata b/app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/macos/Runner/AppDelegate.swift b/app/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/app/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/app/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/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/app/macos/Runner/Base.lproj/MainMenu.xib b/app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/macos/Runner/Configs/AppInfo.xcconfig b/app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..cec41bf --- /dev/null +++ b/app/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 = habitrack_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.habitrackApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/app/macos/Runner/Configs/Debug.xcconfig b/app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/app/macos/Runner/Configs/Release.xcconfig b/app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/app/macos/Runner/Configs/Warnings.xcconfig b/app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/app/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/app/macos/Runner/DebugProfile.entitlements b/app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/app/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/app/macos/Runner/Info.plist b/app/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/app/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/app/macos/Runner/MainFlutterWindow.swift b/app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/app/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/app/macos/Runner/Release.entitlements b/app/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/app/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/app/macos/RunnerTests/RunnerTests.swift b/app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/app/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/app/pubspec.lock b/app/pubspec.lock new file mode 100644 index 0000000..ff1f441 --- /dev/null +++ b/app/pubspec.lock @@ -0,0 +1,1087 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d + url: "https://pub.dev" + source: hosted + version: "0.11.2" + archive: + dependency: transitive + description: + name: archive + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + url: "https://pub.dev" + source: hosted + version: "3.6.1" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + url: "https://pub.dev" + source: hosted + version: "2.4.11" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + url: "https://pub.dev" + source: hosted + version: "7.3.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + custom_lint: + dependency: "direct dev" + description: + name: custom_lint + sha256: "22bd87a362f433ba6aae127a7bac2838645270737f3721b180916d7c5946cb5d" + url: "https://pub.dev" + source: hosted + version: "0.5.11" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: "0d48e002438950f9582e574ef806b2bea5719d8d14c0f9f754fbad729bcf3b19" + url: "https://pub.dev" + source: hosted + version: "0.5.14" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: "2952837953022de610dacb464f045594854ced6506ac7f76af28d4a6490e189b" + url: "https://pub.dev" + source: hosted + version: "0.5.14" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + duration_picker: + dependency: "direct main" + description: + name: duration_picker + sha256: e505a749c93f3218aa4194d339e5d5480d927df23a81f075b5282511f6ac11ab + url: "https://pub.dev" + source: hosted + version: "1.2.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: d0f0d49112f2f4b192481c16d05b6418bd7820e021e265a3c22db98acf7ed7fb + url: "https://pub.dev" + source: hosted + version: "0.68.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_background: + dependency: "direct main" + description: + name: flutter_background + sha256: "035c31a738509d67ee70bbf174e5aa7db462c371e838ec8259700c5c4e7ca17f" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + flutter_launcher_icons: + dependency: "direct dev" + description: + name: flutter_launcher_icons + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" + url: "https://pub.dev" + source: hosted + version: "0.13.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + 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: + dependency: "direct dev" + description: + name: freezed + sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + url: "https://pub.dev" + source: hosted + version: "2.5.2" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: f54946fdb1fa7b01f780841937b1a80783a20b393485f3f6cdf336fd6f4705f2 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554 + url: "https://pub.dev" + source: hosted + version: "14.2.0" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + idb_shim: + dependency: transitive + description: + name: idb_shim + sha256: "8016b1c0bba245d8a88711ce8a801646d0f48c95d3f6a82a6463ef0157846acf" + url: "https://pub.dev" + source: hosted + version: "2.5.0+1" + image: + dependency: "direct main" + description: + name: image + sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8" + url: "https://pub.dev" + source: hosted + version: "4.2.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + isar: + dependency: "direct main" + description: + name: isar + sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + isar_flutter_libs: + dependency: "direct main" + description: + name: isar_flutter_libs + sha256: bc6768cc4b9c61aabff77152e7f33b4b17d2fc93134f7af1c3dd51500fe8d5e8 + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + isar_generator: + dependency: "direct dev" + description: + name: isar_generator + sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" + url: "https://pub.dev" + source: hosted + version: "4.9.0" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + url: "https://pub.dev" + source: hosted + version: "6.8.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + liquid_progress_indicator_v2: + dependency: "direct main" + description: + name: liquid_progress_indicator_v2 + sha256: "6bb2c675bab4936864a63ccd503be417e407974e11c62711917a4006bb9288b8" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + logger: + dependency: "direct main" + description: + name: logger + sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + url: "https://pub.dev" + source: hosted + version: "1.12.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: "direct main" + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a + url: "https://pub.dev" + source: hosted + version: "2.2.6" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + rename: + dependency: "direct main" + description: + name: rename + sha256: "6ef5daf4b11130e71d93630cfb70725e5a35b19039739cfcd2b272c834ba25fe" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + riverpod: + dependency: "direct main" + description: + name: riverpod + sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d + url: "https://pub.dev" + source: hosted + version: "2.5.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: d72d7096964baf288b55619fe48100001fc4564ab7923ed0a7f5c7650e03c0d6 + url: "https://pub.dev" + source: hosted + version: "0.3.4" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c + url: "https://pub.dev" + source: hosted + version: "2.3.5" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: "5b36ad2f2b562cffb37212e8d59390b25499bf045b732276e30a207b16a25f61" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: "70198738c3047ae4f6517ef1a2011a8514a980a52576c7f629a3a08810319a02" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + sembast: + dependency: "direct main" + description: + name: sembast + sha256: "2a768b145bee5c72ae78c2641842ee348a6d345172af1070a497293c44f65ae1" + url: "https://pub.dev" + source: hosted + version: "3.7.1+2" + sembast_web: + dependency: "direct main" + description: + name: sembast_web + sha256: "6b15b49b2a86214130c25c7bdfb3a76eb0f4dc5e43016087f03870bd0bf9ba07" + url: "https://pub.dev" + source: hosted + version: "2.4.0+2" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + url: "https://pub.dev" + source: hosted + version: "0.7.0" + time: + dependency: transitive + description: + name: time + sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + url: "https://pub.dev" + source: hosted + version: "2.1.4" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + very_good_analysis: + dependency: "direct main" + description: + name: very_good_analysis + sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078" + url: "https://pub.dev" + source: hosted + version: "0.1.5" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + win32: + dependency: transitive + description: + name: win32 + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + url: "https://pub.dev" + source: hosted + version: "5.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.4.3 <4.0.0" + flutter: ">=3.22.0" diff --git a/app/pubspec.yaml b/app/pubspec.yaml new file mode 100644 index 0000000..06debd4 --- /dev/null +++ b/app/pubspec.yaml @@ -0,0 +1,73 @@ +name: habitrack_app +description: "A lightweight Habit Tracker." +publish_to: 'none' +version: 0.1.0 + +environment: + sdk: '>=3.4.3 <4.0.0' + +#isar_version: &isar_version 3.1.0 + +flutter_launcher_icons: + android: "launcher_icon" + ios: false + image_path: "assets/icon/logo.png" + min_sdk_android: 21 # android min sdk min:16, default 21 + web: + generate: true + image_path: "assets/icon/logo.png" + background_color: "#hexcode" + theme_color: "#hexcode" + + + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter + flutter_riverpod: ^2.5.1 + freezed_annotation: ^2.4.2 + go_router: ^14.2.0 + #isar: *isar_version + #isar_flutter_libs: *isar_version + intl: ^0.19.0 + liquid_progress_indicator_v2: ^0.5.0 + logger: ^2.3.0 + riverpod_annotation: ^2.3.5 + shared_preferences: ^2.2.3 + duration_picker: ^1.2.0 + isar: ^3.1.0+1 + json_annotation: ^4.9.0 + isar_flutter_libs: ^3.1.0+1 + path_provider: ^2.1.3 + very_good_analysis: ^6.0.0 + sembast: ^3.7.1+2 + path: ^1.9.0 + sembast_web: ^2.4.0+2 + riverpod: ^2.5.1 + fl_chart: ^0.68.0 + image: ^4.0.17 + flutter_background: ^1.2.0 + rename: ^3.0.2 + + + + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^4.0.0 + riverpod_generator: ^2.3.3 + build_runner: ^2.4.11 + custom_lint: ^0.5.11 + riverpod_lint: ^2.1.1 + isar_generator: ^3.1.0+1 + freezed: ^2.5.2 + hive_generator: ^2.0.1 + json_serializable: ^6.8.0 + flutter_launcher_icons: ^0.13.1 +flutter: + generate: true + uses-material-design: true + diff --git a/app/web/favicon.png b/app/web/favicon.png new file mode 100644 index 0000000..146b7fa Binary files /dev/null and b/app/web/favicon.png differ diff --git a/app/web/icons/Icon-192.png b/app/web/icons/Icon-192.png new file mode 100644 index 0000000..088ebee Binary files /dev/null and b/app/web/icons/Icon-192.png differ diff --git a/app/web/icons/Icon-512.png b/app/web/icons/Icon-512.png new file mode 100644 index 0000000..ee656f1 Binary files /dev/null and b/app/web/icons/Icon-512.png differ diff --git a/app/web/icons/Icon-maskable-192.png b/app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..088ebee Binary files /dev/null and b/app/web/icons/Icon-maskable-192.png differ diff --git a/app/web/icons/Icon-maskable-512.png b/app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..ee656f1 Binary files /dev/null and b/app/web/icons/Icon-maskable-512.png differ diff --git a/app/web/index.html b/app/web/index.html new file mode 100644 index 0000000..ba25501 --- /dev/null +++ b/app/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + Habitrack + + + + + + diff --git a/app/web/manifest.json b/app/web/manifest.json new file mode 100644 index 0000000..917d4aa --- /dev/null +++ b/app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "habitrack_app", + "short_name": "habitrack_app", + "start_url": ".", + "display": "standalone", + "background_color": "#hexcode", + "theme_color": "#hexcode", + "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" + } + ] +} \ No newline at end of file diff --git a/app/windows/.gitignore b/app/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/app/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/app/windows/CMakeLists.txt b/app/windows/CMakeLists.txt new file mode 100644 index 0000000..6272894 --- /dev/null +++ b/app/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(habitrack_app 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 "habitrack_app") + +# 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/app/windows/flutter/CMakeLists.txt b/app/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/app/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/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..afc39a1 --- /dev/null +++ b/app/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + IsarFlutterLibsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); +} diff --git a/app/windows/flutter/generated_plugin_registrant.h b/app/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/app/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/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2a57005 --- /dev/null +++ b/app/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + isar_flutter_libs +) + +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/app/windows/runner/CMakeLists.txt b/app/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/app/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/app/windows/runner/Runner.rc b/app/windows/runner/Runner.rc new file mode 100644 index 0000000..bf868b4 --- /dev/null +++ b/app/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", "habitrack_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "habitrack_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "habitrack_app.exe" "\0" + VALUE "ProductName", "habitrack_app" "\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/app/windows/runner/flutter_window.cpp b/app/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/app/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/app/windows/runner/flutter_window.h b/app/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/app/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/app/windows/runner/main.cpp b/app/windows/runner/main.cpp new file mode 100644 index 0000000..b825c83 --- /dev/null +++ b/app/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"habitrack_app", 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/app/windows/runner/resource.h b/app/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/app/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/app/windows/runner/resources/app_icon.ico b/app/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/app/windows/runner/resources/app_icon.ico differ diff --git a/app/windows/runner/runner.exe.manifest b/app/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/app/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/app/windows/runner/utils.cpp b/app/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/app/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/app/windows/runner/utils.h b/app/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/app/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/app/windows/runner/win32_window.cpp b/app/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/app/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/app/windows/runner/win32_window.h b/app/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/app/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_ diff --git a/docs/2-UserResearch.md b/docs/2-UserResearch.md new file mode 100644 index 0000000..d4c541d --- /dev/null +++ b/docs/2-UserResearch.md @@ -0,0 +1,349 @@ +Preamble: +Alles, was Kursiv markiert wurde, stammt aus einer Zeit vor der Aufspaltung der Gruppe und wurde von der anderen Gruppenhälfte gemacht. +Vieles ist aber nicht genau zuzuweisen, da einiges in Zusammenarbeit geschehen ist oder auf der Arbeit von anderen Aufbaut. Es wurde lieber zu viel als zu wenig markiert. + +# User Research + +Document your user research (plan and findings). + + +## Problem + +Junge Menschen, insbesondere solche mit Neurodivergenzen (wie Autismus oder ADHS), haben oft Schwierigkeiten, sich für einen festgelegten Zeitpunkt oder Zeitraum auf ein Thema zu konzentrieren, ohne abgelenkt zu werden, es zu vergessen oder aufzuschieben. + + +## Criteria / Assumptions of our users + +_Es gibt eine App, die es ermöglicht, seine gesetzten Ziele kurz- und mittelfristig zu erreichen und dadurch einen Mehrwert im Alltag zu erlangen._ + +1. _Die App bietet Timer-Widgets für verschiedene Aktionen wie beispielsweise Erinnerungen daran, etwas zu trinken. Diese Widgets können für bestimmte Zeitintervalle aktiviert werden und erinnern den Nutzer durch Töne und/oder Toast-Benachrichtigungen auf dem Bildschirm seines Mobilgeräts._ + +2. _Es gibt eine App, die es ermöglicht, seine gesetzten Ziele kurz- und mittelfristig zu erreichen und dadurch einen Mehrwert im Alltag zu erlangen._ + +3. _Für das Lernen innerhalb eines definierten Zeitraums kann ein zeitbasiertes Punktesystem genutzt werden, um sich in den Pausen abzulenken und motiviert zu bleiben._ + +4. _Alle aktiven Aktivitäten und Widgets liefern Metriken auf einem separaten Bildschirm, sodass der Nutzer jederzeit sehen kann, wie nah er seinen Zielen bereits gekommen ist._ + +_Zusätzlich kann jeder Nutzer einen (oder mehrere) "Buddy" registrieren, der Zugang zu den Metriken hat und ihn motivieren kann, indem er Nachrichten oder Emojis (einschließlich Cheer und Glitzer) sendet._ + + + + +## Hypothesis + +Tracking user habits will increase productivity and lessen wasted time and burnout. + +Tracking user habits will build up consistency, increase productivity, motivation and health as well as lessening time spent on planning the daily workload. + + +## Online Poll Questions + + +### Questions about Scheduling + +Are you planning/scheduling your days? (y/n) + +How far ahead do you plan? [] I do not plan at all. [] A day at most. [] I plan a week in advance. [] I plan up to a month ahead, or longer. + +If you plan your days, how likely are you to stick to that plan? 1 (v) + +Of all the tasks you plan to do at the start of a day, how many of them do you finish at the end of a day? 1 (None) to 5 (All) + + +### Questions about work/life + +Do the following statements apply to you? Rate them on a scale from 1 (Hard Disagree) to 5 (Hard Agree). + +I am prone to losing track of what I want to do in a day. + +I am often unsatisfied with what I achieved that day. + +I regularly spend more time with Video games, Social Media or other sources of distraction than I would like to. + +I am satisfied with my work-life-balance. + +### Questions about Non-Achieving of tasks + +When you do not doing a task, what are the main reasons: +- I forgot +- I found myself demotivated +- I lacked time + + +### Questions about Outward influence + +Do you find yourself achieving better/more when reminded/encouraged? + +Have you worked with other apps to tackle these issues? + +How successful were you? + + +### Personal Questions + +Do you have any suspected or diagnosed Neurodivergencies (Autism, ADHD, ...) + +How old are you? [>14, 14-18, 18-25, 25-35, 35-50, 50+] + + +## User Questions + +### Intro +--- +_Personen aus Studienkontext suchen (bspw. D14, Infotreff, usw) und nach kurzem Interview fragen - HCI erwähnen!_ +_Gespräch mit lockerem Small-Talk beginnen -> angenehme Atmosphere_ +_Wichtig dabei ist:_ +- dass wir uns vorstellen +- der Person keine Zeit rauben +- sympatisch rüberkommen +- nicht aufdringlich sind +- auf die Reaktion der Person warten ohne zu fordern +- erklären was wir von ihr wollen (unser Projekt und das Ziel kurz umreißen) +- deklarieren, dass wir das Interview nur deshalb nutzen, um herauszufinden was der Person guttun könnte und sie gebrauchen könnte + +_KISS (Keep It Simple, Stupid): "Hallo, ich heisse X, haben Sie Zeit fuer einem kurzem interview ueber Ihre Taegesplanung? Wir versuchen gerade eine App dafuer zu entwickeln und waere dankbar, wenn Sie uns eine paar Fragen beantworten koennen."_ + + +### User specific Questions + +- _Wie strukturierst du dein Studium/Alltag? (Persönliche Anekdote einfügen, bspw. Ich schreibe mir Zettel, Kalender, usw.)_ +- _Auf einer Skala von 1 bis 10, wie wichtig ist es für dich, deine Aufgaben und Termine im Voraus zu planen?_ +- _Welche Rolle spielen digitale Technologien und Apps in deinem täglichen Leben?_ + +#### -> Vertiefung der Alltagsorganisation: + +- _Wie priorisierst du normalerweise deine Aufgaben und Verpflichtungen?_ +- _Nutzt du derzeit irgendwelche digitalen Tools oder Apps zur Organisation deines Alltags? Wenn ja, welche und warum?_ +- _Was sind die größten Herausforderungen oder Frustrationen, die du mit den aktuellen Lösungen erlebst?_ + +### Pains/Needs + +- _Passiert es dir auch manchmal, dass du die alltäglichen Aufgaben beiseite schiebst oder vergisst?_ +- _Also ich schiebe den Wäscheberg manchmal zwei Wochen vor mich her, mit der Konsequenz, dass der Schrank manchmal leer ist - hast du auch ab und zu solche Momente?_ +- _Gibt es für dich eine Sache, oder eine Aufgabe, die immer zu kurz kommt - wie z.B. Wasser zu trinken - und du würdest es gerne ändern, weißt aber nicht wie?_ +- _Wie wichtig ist es für dich, eine Balance zwischen (Arbeit,) Studium und Freizeitaktivitäten zu finden? Welche Hindernisse treten dabei oft auf?_ +- _Prokastinierst du, da Sie keine Motivation haben, um an deine Plan zu halten?_ + + +#### Weitere Bedürfnisse: +- _Beschreibe einen typischen Tag, an dem du Schwierigkeiten hast, deine Aufgaben zu bewältigen. Was fehlt dir in solchen Momenten?_ +- _Gibt es spezifische Aufgaben oder Aktivitäten, die du besonders oft vergisst oder vernachlässigst?_ +- _Wie gehst du mit Stress um, der durch unerledigte Aufgaben verursacht wird?_ +- _Hast du jemals versucht, verschiedene Methoden oder Techniken (apps, TODO lists, usw) zur Aufgabenorganisation auszuprobieren? - Wenn ja, welche haben für dich am besten funktioniert?_ + + + + +### Addressing the solution +--- +- _Könntest du dir vorstellen, deine täglichen Aufgaben visuell oder grafisch zu organisieren? Zum Beispiel mit einem Kalender oder einem Kanban-Board, um Erinnerungen und Aufgaben zu verwalten?_ +- _Mal angenommen, du hättest eine App, in der du deine ganzen Aufgaben eintragen und verwalten würdest - wäre das eine Erleichterung für dich?_ +- _Manche Ziele sind ja schwer alleine zu erreichen, sprichst du manchmal mit deinen Freunden oder Eltern über ein bestimmtes Ziel und holst dir da eine mentale Unterstützung?_ +- _Wie wäre es, wenn du deinen Besten Freund mit ins Boot holen würdest, der dich dabei unterstützt; also indem er sieht wie weit du gekommen bist und dir jederzeit Mut machen könnte? Kannst du dir vorstellen, das in besagter App als Funktion wiederzufinden?_ +- _Was wäre für dich ein absolutes Must-Have-Kriterium in der App, damit du sie in deinem Alltag integrieren könntest? +Stell dir vor, du hast in der App Widgets für bestimmte Aufgaben, was müsste das Widget deiner Meinung nach können?_ + +- _Wie wichtig ist die Benutzeroberfläche und das Design einer App für dich? Was macht eine App angenehm zu nutzen?_ +- _Wie wichtig ist es für dich, dass eine Organisations-App dich bei der Verfolgung langfristiger Ziele und Projekte unterstützt?_ +- _Wie wichtig ist es für dich, dass die App einfach und intuitiv zu bedienen ist, auch ohne eine ausführliche Anleitung?_ + +### Ende/Zusammenfassung +--- +- _Hast du noch andere Ideen oder Features für diese App, die du nützlich finden würdest?_ +- _Vielen Dank für deine Zeit und dein Feedback. Hier ist eine kurze Zusammenfassung unserer Besprechung: [Füge Zusammenfassung hier ein]_. +- _Gibt es noch etwas, das du hinzufügen möchtest oder an das du gerade denkst? Ok, dann sind wir fertig. Das war echt super, dass du mir soviel input gegeben hast._ +_(Wir können gern Whatsapp / Discord austauschen und ich geb dir bescheid, wenn die App fertig ist)_ + + + +## Interview Zusammenfassungen + +### Interview 1 + +- _Ehemaliger Dualer Student Informatik; im 3. Fachsemester_ +- _Verbleibt an der Hochschule, weil das Umfeld motiviert, seine Aufgaben zu erledigen._ +- _Prokrastination ist der größte Feind, daher Mediennutzung nur noch Zweckgebunden eingesetzt und andere Hobbies aufgegeben._ +- _Lässt sich nicht durch Apps oder andere vom Spielen abbringen._ +- _Denkt, die Pomodoro Funktion ist redundant, da man selbst genug an Pausen denkt._ +- _Timelines und Boards helfen sich selbst einen Überblick über die erbrachte Leistung zu verschaffen._ +- _Die Verknüpfung der App mit anderen Apps, wie z.B. Health oder Google-Calendar würde sehr praktisch sein, da man nicht immer die App wechseln müsste um sich auf den neusten Stand zu bringen._ +- _Ein Newsfeed mit neusten Themen z.B. IT-Sicherheit wäre ebenfalls super._ +- > _"Die App könnte mir dabei helfen, motivierter zu sein, indem ein Buddy mir das Gefühl gibt, nicht alleine zu sein."_ + + +### Interview 2 + +- _Student im 4. Semester Informatik, ADHS_ +- _Fehlt Struktur im Alltag, trotz mehrerer Anläufe mit verschiedener Apps._ +- _Priorisierung und Aufgabenplanung derzeit nur theoretisch._ +- _Der Haushalt wird oft aufgeschoben._ +- _Ist in einer Beziehung, in der die Planung auch immer den Partner mitbetrifft._ +- _Denkt, Pomodoro ist nicht geeignet, da man entweder selbst die freie Zeit bestimmt, oder in einem "Flow" zum Arbeiten ist, den man lieber nicht unterbricht._ +- _Zwei Kategorien: short- und long-term würden bei den Aufgaben gut sein. Insbesondere long-term Aufgaben sollten immer sichtbar sein._ +- _Timelines und Boards helfen den Überblick zu behalten. Zusätzlich sollten einige Aufgaben auf dem Home-Screen abgebildet werden._ +- _Benachrichtigungen sind ein wichtiges Thema, denn je öfter an eine Aufgabe erinnert wird, desto weniger kann es vergessen werden._ +- > _"Ein Buddy, der mich bemuttert, statt mir bei den Aufgaben zu helfen, brauche ich nicht. Dafür wäre eine Spracherkennung für die Aufgabenerstellung sehr nützlich."_ + + + + +## **Persona + +### Ernie +
+ ![Ernie](https://image.vip.de/23176252/t/49/v2/w960/r1.5/-/kultserie-stromberg-was-wurde-aus-christoph-maria-herbst-diana-staehly---co--jpg--article-image-56279185-.jpg){width=200} +
+ "Die App kann mich nicht kontrollieren, da bin ich Egoistisch" - Ernie +
+
+ +_**Alter**: Zwischen 20 und 25 Jahren_ +_**Ausbildung**: Drittes Fachsemester / 11tes Studiensemester (hat früher ein Dual bei einem Telekommunikationsanbieter studiert)_ +**_Lebensstil_**: +- _Ernie führt einen eher unstrukturierten Alltag, versucht jedoch, wichtige Aufgaben in der Universität zu erledigen._ +- _Die Alltagsplanung ist ihm sehr wichtig, und er ist inflexibel, wenn es um die Umsetzung seiner Pläne geht._ +- _Er hat vor einem Jahr seine Nutzung von Social Media und YouTube stark reduziert und konzentriert sich nun auf grundlegende digitale Anwendungen wie Organisations- und Home-Automatisierungs-Apps._ + +**_Herausforderungen und Bedürfnisse_**: +- _Ernie hat Schwierigkeiten, seine Aufgaben zu organisieren und neigt dazu, zu prokrastinieren._ +- _Er legt Wert auf eine ausgewogene Work-Life-Balance und investiert mittlerweile mehr Zeit in seine Freizeit._ +- _Motivationssteigerung und eine verbesserte Produktivität sind für ihn von Interesse, insbesondere durch Tools, die ihm helfen, seine Zeit effektiver zu nutzen und gesunde Gewohnheiten zu fördern._ + +**_Präferenzen und Wünsche_**: +- _Ernie bevorzugt einfache und anpassbare Benutzererfahrung._ +- _Er würde es begrüßen, Funktionen wie eine ToDo-Liste mit Timelines und wiederkehrende Aufgaben in einer App zu haben._ +- _Zusätzliche Funktionen wie ein Gesundheitstracker, Integration mit Kalendern und ein Newsfeed zu aktuellen Themen wären für ihn interessant._ +- _Das Buddy-System, das ihm Motivation und ein Gemeinschaftsgefühl bietet, könnte für ihn besonders ansprechend sein._ + + +### Bert + +
+ ![Bert](https://images.berliner-kurier.de/2014/1/26/f722151e-efea-304d-b625-9c62ae936016.jpg?auto=format&fit=max&w=1880&auto=compress){width=200} +
+ "...aber die Freundin stört oft die Pläne." - Bert +
+
+ +_**Alter**: Zwischen 20 und 25 Jahren_ +_**Studium**: Informatik mit Schwerpunkt auf Künstliche Maschinenintelligenz, viertes Semester/ zweites Fachsemester_ +_**Lebensstil**:_ +- _Bert verwendet Produktivitätstools wie Notion und den iOS Kalender, allerdings nutzt er diese nicht sehr aktiv._ +- _Sein Alltag ist nicht stark strukturiert, was ihm Unzufriedenheit bereitet._ + +**_Herausforderungen und Bedürfnisse_**: +- _Bert sucht aktiv nach neuen Apps, um seinen Alltag besser strukturieren zu können._ +- _Er hat Schwierigkeiten mit der Motivation, insbesondere bei Haushaltsaufgaben, und verschiebt diese oft._ +- _Er wünscht sich Möglichkeiten, gemeinsam mit seiner Freundin Planungen vornehmen zu können, was auf den Bedarf an kollaborativen Funktionen hinweist._ + +**_Präferenzen und Wünsche_**: +- _Die Customizability und Variabilität von Notion gefallen ihm besonders gut, da er verschiedene Vorlagen miteinander verlinken kann._ +- _Ein Buddy-System empfindet Bert als eher störend; er präferiert automatisierte Systeme, die seine Motivation unterstützen könnten._ +- _Er interessiert sich für Widgets, die eine direkte Einsicht in seine Produktivität und Freizeit bieten, wie ein Timer-Widget, das Arbeits- und Belohnungszeiten darstellt._ +- _Eine Übersicht über tägliche Ziele und ein Wassertrink-Widget direkt auf dem Home-Screen würden ihm ebenfalls zusagen._ + +**_Technische Vorlieben_**: +- _Er benötigt schnellen Zugriff auf Übersichtsseiten und viele, penetrante Benachrichtigungen aufgrund seiner ADHS._ +- _Bert interessiert sich für Alltagsorganisation und würde es begrüßen, langfristige Projekte in diese Organisation mit einzubeziehen._ +- _Eine Spracherkennung für das schnelle Hinzufügen von Aufgaben wäre eine hilfreiche Funktion._** + +_ + + +## MVP + +![demo0](assets/images/mvp_table.png) + + +An der Tabelle ist zu erkennen, dass sowohl Ernie als auch Bert das Wasser-Widget haben wollen. Dementsprechend ist das unser Höchstpriorisiertes Feature. Danach kommt das Timer-Widget. Das Task-Widget ist für Ernie Relevant, für Bert aber nicht. Ernie findet das Buddy-System gut, Bert aber nicht. +Niemand mag Pomodoro. + + + +## Scenarios + +### Tom +_Tom, ein Informatikstudent im 3. Semester wird in der Mensa von seinem Kollegen Tim gefragt, ob sie beide am verlängerten Wochenende raften wollen. Tom schaut kurz auf sein Mobile und entdeckt eine Aufgabe auf dem Home-Screen, die er noch gar nicht angefangen hat - und die Abgabe als PVL ist Dienstag. Dank unserer App hat Tom das Studium erfolgreich abgeschlossen und ist hinterher 3 Wochen lang mit Tim raften gegangen._ + +### Tina +_Tina ist eine beschäftigte Studentin mit einem vollen Stundenplan aus Vorlesungen, Hausarbeiten und außeruniversitären Aktivitäten. Sie kämpft damit, organisiert zu bleiben und vergisst oft wichtige Fristen. Tina beschließt, eine Produktivitäts-App zu verwenden, um ihre Zeit effektiver zu managen. Sie lädt die App herunter und beginnt, deren Funktionen zu erkunden, auf der Suche nach Tools, die ihr helfen, Aufgaben zu priorisieren und ihren Fortschritt zu verfolgen. Tina richtet Benachrichtigungen ein, um sich an bevorstehende Deadlines zu erinnern, und nutzt die Kalenderansicht der App, um ihre Woche zu planen. Mit der Hilfe der App fühlt sich Tina mehr in der Lage, ihren Zeitplan zu kontrollieren und ist zuversichtlicher, ihre Verpflichtungen einhalten zu können._ + + + + + + + +## Mkdocs snippets +- unordered list with - and four spaces for sub list +- ordered list with 1. +- links with [mkdocs-material](https://squidfunk.github.io/mkdocs-material/reference/) +- references defined in docs/lit.bib may be referenced with [@hciscript] +- reference a chapter of your docs [demo](./3-Design.md#demo) +- code snippets + +```dart +Future printOrderMessage() async { + print('Awaiting user order...'); + var order = await fetchUserOrder(); + print('Your order is: $order'); +} +``` + + +/* +- You may include images like this + +![demo1](assets/images/mobile.jpg) + +![demo2](assets/images/mobile.jpg){: height="5em" width="5em"} + + + +
+ ![UX Methods](https://media.nngroup.com/media/editor/2017/01/12/ux_methods_activities_nng_800px.png){width=200} +
+ UX Research Cheat Sheet: UX Methods +
+
+ +- admonitions + +!!! note + this is a note + +!!! abstract "title" + this is an admonition with title + +!!! info "" + an info without title + +!!! tip + a tip + +!!! example + an example + + +!!! warning + a warning + +!!! cite + "this is a cite"[@hciscript] + +- [mermaid graphics](https://mermaid.js.org/intro/) + +``` mermaid +graph LR + A[Start] --> B[stakeholders] --> C[problem and context] --> D[classes of users] -->E[hypothesis] --> F[interview questions]; + D --> G[persona]; + F --> G; + C --> H[scenarios]; + F --> H; + G --> H; +``` + +- [emojis](https://squidfunk.github.io/mkdocs-material/reference/icons-emojis/) + - :smile: + - :video_camera: + - :fontawesome-solid-user: diff --git a/docs/3-Design.md b/docs/3-Design.md new file mode 100644 index 0000000..71ed13e --- /dev/null +++ b/docs/3-Design.md @@ -0,0 +1,80 @@ +# Design +Document your design process, from paper prototypes to figma. + + +## Name und Konzept + +Habitrack - hilft dir, Gewohnheiten aufzubauen, nicht zu vergessen und Fortschritt zu tracken. Verschiedene Widgets helfen dir bei Verschiedenen Gewohnheiten, so zum Beispiel das ausreichenede Wassertrinken, einnehmen von wichtigen Kommunikationen oder ausführen von einmaligen oder wiederkehrenden Aufgaben. + + + +## Paper Prototyping +![PP1](assets/images/hci_paper_prototype1.jpg) ![PP2](assets/images/hci_paper_prototype2.jpg) + + +Im Papierprototypen wurden verschiedene Arten getestet, die Widgets im Homescreen darzustellen. Zum einen wurde ein minimalistischer List Entry vorgeschlagen, der den Namen und eine kleine Statusanzeige des Widgets enthält. Zudem wurde eine "Card"-Variante getestet, bei der das Widget mit vollen Daten und reduzierten Bedienmöglichkeiten zusammenspielt. Es gibt einen Desginvorschlag für den Statistik-Teil, bei dem man sich die Statistiken der Letzen Tage ansehen kann. +Für das Wasser- und Timer-Widget wurden zudem Vollbild-Designs vorgeschlagen. + +## Tasks + +### Task 1: Timer-Widget +Nachdem du von der App erfahren hast, hältst du das für die perfekte Gelegenheit, um anzufangen, Klavier zu lernen. Deshalb möchtest du ein Widget anlegen, dass dich jeden Tag daran erinnert, mindestens eine Stunde Klavier zu spielen. + +### Task 2: Hydrations-Widget +Deine Ärztin hat dir gesagt, dass du zu wenig Wasser trinkst. Deshalb willst du ein Widget anlegen, das dir hilft, zumindest 2,5 Liter Wasser am Tag zu trinken. + +### Task 3: Medikations-Widget +Du möchtest täglich eine Erinnerung, deine morgendlichen und abendlichen Tabletten einzunehmen: Orylmyte um 9 Uhr, und Melatonin um 20 Uhr. + + +## Logo +![LOGO](assets/images/Habitrack_Logo.png) + +Das Logo für unsere App ist ein Stilisierter Text von unserem App-Namen. Der Hintergrund stellt eine PostIt-Note mit Linen und Kaffee-Flecken dar. Die Farbkombination aus Rot und Schwarz soll Fokus ausstrahlen, mit dem man den Habits auf den Grund geht. Das a von Track ist als Fadenkreuz stilisiert. + +## Farben +Wir haben für die Farben die SeedColor purple genommen. Das ist logischerweise nicht sonderlich kreativ. Wir hatten eine andere Farbpalette herausgearbeitet, die wir auch in den Issues besprochen hatten. Allerdings haben wir uns während des Entwickelns mit dem einfachen Lila angefreundet und jetzt gefällt es uns besser, da Lila schlichtweg eine sehr schöne und angenehme Farbe ist. + + +## User Flow Diagram + +![UFD](assets/images/UserFlowDiagram.png) + +Im User Flow Diagram kann man die Aufteilung der App in zwei Hauptbereiche erkennen - die Widget-Wand und das Statistik-Dashboard. Diese beiden Hauptbereiche sind über eine Bottom-Navigation-Bar zu erreichen. Die Hauptbereiche sind Fully Connected. + +## Data Flow Diagram + +![DFD](assets/images/DataFlowDiagram.png) + +Das Data Flow Diagram beschreibt, wie aus den Funktionswidgets die Daten genommen werden, die dann in den Statistik-Widgets im Dashboard dargestellt werden. Die Funktionswidgets implementieren alle eine Methode, mittels derer man den täglichen Fortschritt zum Ziel auslesen kann. Dieser wird dann auf der Festplatte abgespeichert und über die Zeit hinweg akkumuliert. Mit diesen Daten werden die Statistik-Widgets dann gefüttert. + + +## Figma Prototype + +![F1](assets/images/FIGMA1.png) + +Im Prototypen für die Widgets war geplant, dass das Wasser-Widget noch eine echte Flasche sei. Zudem wurden Widgets über einen Swipe nach Links gelöscht.(inspiriert durch Tinder) + +![F2](assets/images/FIGMA2.png) + +Die Einstellungen waren kein Pop-Up, sondern eine eigene Seite. Die Buttons waren oben und nicht Unten. Zudem Gab es im Allgemeinen noch mehr Einstellungen für die Widgets. + +![F3](assets/images/FIGMA3.png) + +Das Timer-Widget ist, bis auf das genaue Design, größtenteils Identisch geblieben. Es sollte jedoch einstellbar sein, ob sich das Widget täglich oder Wöchentlich zurücksetzt. + + +![F4](assets/images/FIGMA4.png) + +Das Task-Widget sollte ursprünglich in heutige, morgige Tasks usw. unterteilt sein. Tasks sollten sich zudem wiederholen können. + + + +## Was gut und weniger gut lief + +Gut liefen in der Entwicklung meißt die individuellen Arbeitsphasen. Dabei ist meistens ein Mehrwert für das Projekt entstanden. + +Weniger gut lief die Kommunikation in der Gruppe, was im Endeffekt auch zur Aufspaltung der Vierergruppe führte. + + + diff --git a/docs/4-Implementation.md b/docs/4-Implementation.md new file mode 100644 index 0000000..b74febc --- /dev/null +++ b/docs/4-Implementation.md @@ -0,0 +1,38 @@ +# Implementation + +Unsere Code-Struktur ist im Laufe der Entwicklung mehrere Wandel durchlaufen. +Die Erste Version ist durch Folgendes UML-Klassendiagramm dargestellt: + + + + +# Erstes Design + +![UFD](assets/images/UML1.png) + +Dieses Modell verwendet eine Abstrakte Widget-Basisklasse, von der die Einzelnen Funktionswidgets erben. Sie ist mit Einigen Basisfunktionen versehen, die teilweise noch nicht implementiert werden. Die Build-Funktion baut alle Teile des Widgets, die alle Funktions-Widgets gemein haben: Die Aufklappbare Progress-Bar, den Widget-Titel und das Einstellungsmenu. + +Um die Einstellungen Anpassen zu können, gibt es die Klasse WidgetSettings. Davon wird eine in WidgetBase gespeichert. WidgetSettings besteht aus einer Liste an SettingEntries. Dieses Design wurde aus einem Projekt für die Arbeit genommen, in dem eine Einfache GUI in TKInter Programmiert wurde. SettingEntries ist ein Interface mit der Build-Methode, sowie Value Gettern/Settern. Die einzelnen Subklassen stellen verschiedene Einstellungstypen dar: Eingabe von einem Datum, von Text, von Zahlen, oder von Zahlen in einem Raum mit einem Slider. WidgetSettings war in diesem Design der Primäre Ort, in dem die Widget-Einstellungen gespeichert wurden, und es wurde über eine Map von String auf SettingEntry auf sie zugegriffen. + + +Jedes Funktionswidget hat ein Content-Widget, in welchem der Teil des Widgets gebaut wird, der erscheint, wenn man das Widget aufklappt. + +Diese Klassenstruktur war gut, um Redundanzen im Code zu minimieren. Allerdings war sie auch etwas überkompliziert, und hatte die Persistenz über Sembast und das State Management mit Riverpod noch nicht wirklich mit einberechnet. Dies hat zu einigen Fehlern geführt, weswegen sie überarbeitet wurde. + + + +# Zweites Design + +![UFD](assets/images/UML2.png) + +Das Zweite Design ist eine Menge der Komplexität des Ersten Designs losgeworden. Die Funktionswidgets erben nicht mehr von einer Gemeinsamen Basisklasse, enthalten dafür aber eine Menge duplizierten Code. Dadurch, dass alles in einer Klasse gemanaged wird, wird die Persistenz erleichert. + +Jedes Funktionswidget hat nun ein zugehöriges Item, welches den State des Widgets speichert. Dazu gehören die Einstellungen, der Fortschritt, und alles was sonst benötigt wird. Diese Items können über ein Riverpod-Provider aus der Sembast-Datenbank geholt und dahin auch wieder gespeichert werden. + + + + +# Was gut und weniger Gut lief + +Im Laufe der Programmierung wurden wir mit Flutter und Dart immer mehr vertraut, was es uns ermöglicht hat, Dinge zu implementieren, die am Anfang unmöglich erschienen. Allerdings wurden deswegen am Anfang Designentscheidungen getroffen, die uns viel Zeit gekostet haben und so im Endeffekt nicht funktionierten. Zwar haben wir viel dabei gelernt, trotzdem war die Zeit eine sehr wertvolle Ressource. +Es gab demnach einige Probleme mit der Persistenz-Machung unseres Ursprünglichen Designs, weswegen vieles überholt werden musste. Da Sembast keine Widgets direkt speichern können, müssen die State als getrennte Objekte gespeichert werden. Dann müssen die Widgets die state vom Datenbank laden und die jeweilige Daten populieren. Wir hatten auch Probleme bei dem generierung von `.g.dart` Dateien mit freezed; das hat eine Weile gedauert, vor wir es lösen könnten. Sembast war danach relativ einfach anzuwenden, allerdings müssten wir "hacky" methoden manchmals verwenden, wie z.b. die Speicherung vom individuelle task items als Strings, da wir nicht composition mit freezed zum laufen bringen können. diff --git a/docs/5-UsabilityEvaluation.md b/docs/5-UsabilityEvaluation.md new file mode 100644 index 0000000..f0f1e9b --- /dev/null +++ b/docs/5-UsabilityEvaluation.md @@ -0,0 +1,134 @@ +# Usability Evaluation + + +# Cognitive Walkthrough +Task Scenario: Einrichten eines Wasser-Widgets, umbenennen auf Tagesbedarf Pepsi, Trinken von 1.5 von 3 Litern +Optimal Steps: +- App Öffnen (Schon getan) +- Widget-Add-Button oben links drücken +- "Wasser-Widget Hinzufügen"-Button drücken +- Per druck auf Pfeil Nach unten beim Neu erstellten Widget aufklappen +- Per Druck auf Zahnrad in die Widget-Einstellungen gehen +- Ins Textfeld "Name des Widgets" "Tagesbedarf Pepsi" eintragen +- Tagesziel per Schieberegler auf 3L setzen +- Einstellungen Speichern +- 6 mal 250ml-Button drücken ODER "+ beliebig" Button drücken und 1500 eingeben und Speichern + + +# Heuristic Evaluation + +### Visibility of System Status +Im eingeklappten Modus kann man schnell auf einen Blick für alle angelegten Widgets erkennen, wie weit diese Abgeschlossen sind. Auch im Dashboard ist das auf einen Blick im Graphen gut erkennbar dank Linie mit Zielwert und Jetzigem Wert in unterschiedlichen Farben. + +### Match Between System & Real World +Neue Task Defaulten immer zum derzeitigen Datum. Timer laufen auch im Hintergrund weiter. + +### User Control & Freedom +Es ist Möglich, beliebig viele Widgets anzulegen. Bei allen diesen Widgets lässt sich der Name und verschiedene Einstellungen individualisieren. Damit kann der Nutzer diese App für alle Möglichen Zwecke nutzen. Beispiel: Die Task-Liste kann eine Einkaufsliste, eine Plan-To-Watch-Liste oder an sonstige Dinge erinnern. Timer-Widgets können verschieden benannt werden und so beim Klavier üben, beim Arbeiten oder beim Meditieren Verwendet werden und hier Fortschritt tracken. Zudem lassen sich die Statistiken Wochen- oder Tagesweise darstellen und man kann sich vergangene Wochen ansehen. + +### Consistency and Standards +Die App speichert alle Daten persistent ab. Sogar Aufgeklappte Widgets werden Gespeichert, was ein direktes Benutzen beim App-Start erlaubt. + +### Error Prevention +An einigen Stellen werden User Errors vorgebeugt. Zum Beispiel kann man die Dauer eines Timers nur einstellen, wenn dieser Gestoppt ist. Numerische Eingabefelder haben zudem keine Angezeigten Buchstaben. + +### Recognition rather than Recall +An keiner Stelle muss der Nutzer sich Werte merken, die er dann an einem anderen Screen braucht. Die Einstellungen der Widgets sind bei allen Widgets am selben Ort und passen auf eine Seite. + +### Flexibility and Efficiency of Use +Die Widgets sind gut Customisable und damit flexibel zu benutzen. + +### Aestetic and Minimalist Design +Das Design ist schlicht gehalten, mit einer Gediegenen Color-Palette und freundlichen Formen. Durch einheitliches Theming lullt einen die App in einen Charmanten Farbmantel. Die App kommt ohne Schnick-Schnack aus und konzentriert sich auf die MVPs, die Core Features. + +### Help Users With Errors +Die App ist im Grunde nicht schwer zu bedienen, nimmt die Nutzer aber auch nicht an die Hand. Es gibt keine Tutorials und Tooltips. Damit muss der Nutzer ein wenig rum-probieren, um die App gescheit zu benutzen. + + + +# Thinking-Aloud-Tests + +Die Thinking-Aloud-Tests sind ohne festes Skript erfolgt, da wir es für besser hielten, individuell und spontan auf die Partizipenten zu reagieren. Wir haben uns als Entwickler eine App für HCI vorgestellt und gefragt, ob die Partizipenten eine halbe Stunde Zeit für einen Usability-Test hatten. Die Interviews sind, wie die User-Research-Interviews, im Infotreff in D14 abgehalten worden. + +Wir haben kurz das Grund-Konzept unserer App vorgestellt und dann die App mit Aufgaben an die Teilnehmer gegeben. Die Aufgaben waren die Folgenden: + +### 1: Füge ein Task-Widget hinzu, und dann darin eine Tasks. Passe das Datum auf heute in 3 Wochen an und schließe die Task anschließend ab. Lösche die Task. + +### 2: Füge ein Wasser-Widget hinzu. Ändere das Ziel auf 3 Liter, und trinke dann 1.5 Liter davon. + +### 3: Füge ein Timer-Widget hinzu. Stelle den Timer auf 30 Minuten und lasse ihn durchlaufen. +(Zeit war dafür um den Faktor 60 beschleunigt.) + +### 4: Lösche alle vorher angelegten Widgets. + + + +## Teilnehmer 1: +### 1: +Der Hinzufügen-Button wurde nicht direkt gefunden, und es wurde im Dashboard geschaut. Dabei wurde bemäkelt, dass diese nicht klar voneinander getrennt seien. Denn Button sollte man klarer als Button erkennbar machen (Zu dieser Zeit hatte der Button noch keine Outline.) + +Beim Auswählen des Datums sollte der Button zum auswählen des Datums auch klarer als Button erkennbar gemacht werden. + +### 2: +Wieder wurde sich Outlines um die Add-Buttons gewünscht. Die Buttons wurden erst als Skala interpretiert. Es wurde sich eine Skala für das Wasserglas gewünscht. + +Zudem war die Overfill-Mechanik für den Teilnehmer eher verwirrend. Es wurde sich gewünscht, dass der Custom-Amount-Button den letzten Eingabewert speichert. Zudem war die Teilnehmerin kein Fan davon, dass man im Aussuchen des Tagesziels durch den Slider beschränkt ist, wenngleich das feature an sich für den Sommer praktisch sei. + +### 3: +Das Bedienen des Timers verlief ohne Probleme. Es wurde sich eine Abfrage am Ende gewünscht, ob man das auch tatsächlich für die Zeit produktiv war. + +### 4: +Das Deleten wurde nicht direkt gefunden, und ein Hinweis auf die Widget-Einstellungen war vonnöten. Es wurde zunächst versucht, das Widget mit Tippen und Halten in einen Delete-baren Zustand zu versetzen. + + + +## Teilnehmer 2: +Glücklicherweise ist es uns gelungen, in Interview 2 die Person zu interviewen, die wir auch schon in Interview 1 für die User Research interviewt hatten. Damit konnte die Person sagen, inwieweit sich ihre Anforderungen erfüllt haben. + +### 1: +Wieder wurde bemängelt, dass der Add-Widget-Button schwer zu finden sei. Ansonsten "tut das Task-Widget, was es soll". Bedienungsprobleme gab es keine. + +### 2: +Die Glas-Optik der Wasser-Anzeige wurde gelobt. Der Slider in den Einstellungen war okay, obwohl die Person normalerweise kein Fan von Schiebereglern sei, da der Rahmen (0-4L) eh selten überschritten werden sollte. +Zudem wurde die Anordnung der Buttons bemängelt (100ml über 250ml), der Punkt wurde aber Fallen gelassen, nachdem klar wurde, dass man die Menge, die die einzelnen Buttons hinzufügen, in den Einstellungen verändern kann. Bedienungsprobleme gab es keine. +"Ich kann mich nicht beschweren" (Größtes Lob, was eine deutsche Person sagen kann) + +### 3: +Der Stop-Button sollte aufgrund besserer Verständnis zu "Reset" umbenannt werden. Die Duration Dial hat keine Probleme in der Bediehnung verursacht. + +Das Widget "Sieht eigentlich ziemlich gut aus" und "sei ähnlich wie Gewünscht". + +### 4: +Wieder wurde zunächst versucht, das Widget mit Tippen und Halten in einen Delete-baren Zustand zu versetzen. Danach wurde aber schnell der Weg in die Einstellungen gefunden. + + +## Interpretation +Alles in allem wurden ein paar Schwachstellen bei den Usability Reviews klar, die wir aufgrund von Betriebsblindheit übersehen hatten. Beispiele dafür waren nicht deutlich genug gekennzeichnete Buttons. Diese haben wir im Anschluss behoben. Ansonsten gab es noch ein paar optische Kritiken und Feature Requests, denen wir nicht nachgegangen sind. Die Bedienung der App lief aber größtenteils Fehlerfrei. Dies ist ohne Frage nicht zuletzt dem zu verdanken, dass wir die Interviews mit Technik-Affinen Informatik-Studierenden durchgeführt haben. + + + + +# Accessibilty Scanner +Bei dem benutzen des Accessibility Scanners sind 146 Vorschläge über 37 Screens angefallen. Einige davon waren Duplikate. Die Vorschläge ließen sich in Drei Kategorien einteilen: + +### 1: Objektlabel +Ein Icon-Button hatte kein Bedienungshilfen-Label, was das Korrekte anzeigen mit Sprachausgabe unmöglich macht. + +### 2: Text nicht Enthalten +Ein Text, der Auf dem Bildschirm erkannt wurde, ist so nicht im Bedienungshilfen-Label vorhanden. + +### 3: Berührungszielbereich +Ein antippbarer Bereich ist kleiner als 45dp x 41 dp. Damit ist er schwer anzutippen. + +### Fazit +Alle diese Drei Probleme sind recht leicht zu beheben, indem Bedienungshilfen-Labels hinzugefügt werden oder Objekter höher Skaliert werden. Leider hat das Hinzufügen von Bedienungshilfenlabels den Rahmen unseres Projektes gesprengt, ansonsten wäre das eine gute Idee gewesen. + + +# Development Process +Unser Process ist am ehesten dem Agile-UX-Prinzip gefolgt. Allerdings haben wir viel zu lange am Code gesessen, und damit wenig Zeit für die Dokumentation gehabt. Damit war offensichtlich, dass wir wenig Erfahrung mit diesen Arbeitsweisen haben und diese nur bedingt anwenden konnten. + +# Was gut und weniger gut lief +Die Interviews liefen ziemlich gut - dass wir einen der initialen Interview-Partner wieder interviewen konnten war ein Glücksfall. Allerdings haben wir zu lange am Code gesessen und Code Freeze war für uns eher eine Legende... Dass sollte beim nächsten mal definitiv besser gemacht werden. + + + diff --git a/docs/HabiTrack_Presentation.pdf b/docs/HabiTrack_Presentation.pdf new file mode 100644 index 0000000..dcbc3d9 Binary files /dev/null and b/docs/HabiTrack_Presentation.pdf differ diff --git a/docs/Interviews/.gitkeep b/docs/Interviews/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/Interviews/interview_1_notes.md b/docs/Interviews/interview_1_notes.md new file mode 100644 index 0000000..ff30d49 --- /dev/null +++ b/docs/Interviews/interview_1_notes.md @@ -0,0 +1,58 @@ +Interview Notes + +Start at 13:40 +App-Konzept und basic informationen dazu erklärt. + +Interviewee: Drittes Fachsemester / 11tes Studiensemester, hat früher Dualstudium gemacht. +Counter qs: make an app for HCI, why interview? Personas, etc explained, features from user research + +Q: Wie strukturierst du deinem alltag? (Anekdote gegeben) +A: Alltag eher unorganisiert. Der Uni Kalender (OBS) wird genutzt. Lebt Woche zu Woche, Versucht Aufgaben am besten in der Uni abzuarbeiten. Wichtige Dinge werden in die allgemeinen Notizen geschrieben. + + +Q: Wie wichtig ist dir Alltagsplanung? +A: Sehr wichtig. Bin inflexibel bei der Planung. Pläne werden durchgeführt, auch wenn diese nicht vernünftig sind. + +Q: Welche Rolle spielen Apps und digitale Medien im Alltag? +A: Vor einem Jahr wurde Social-Media/Youtube-Nutzung wegen verringerter aufmerksamkeit stark reduziert +=> die Nutzung ist zurückgegangen: Nur die Basics werden genutzt. (Organisitorisches, Standardapps, home-automation-apps (Staubsauger)) + +Q: Werden Apps speziell für Organisation genutzt? +A: Teams-Kalendar und die Notizen-App (fürs Einkaufen z.B.) + +Q: Schiebst/Vergisst du manchmal Aufgaben? +A: Im letzten Semester wurde viel prokrastiniert. Als es im dualen Studium keine PVLs gab (andere Uni) wurde alles in der letzten Woche gemacht. Termine wurden immerhin eingehalten, aber es könnte besser sein. Mittlerweile ist es besser geworden mit mehr Motivation. + +Q: Gibt es Probleme mit alltäglichen Aufgaben? +A: Nicht so viele, kriegt dies ganz gut hin. Früher war kaum Zeit für Hobbies da wegen der Arbeit/ dem Studium. Es wurde viel am Handy prokastiniert. RIP Hobbies. Die Organisation war eher schlecht. + +Q: Wie wichtig ist dir Work-Life-Balance? +A: Freizeit ist sehr wichtig. Mittlerweile ist die Balance recht gut. + +Q: Was hälst du von den Folgenden Ideen? +Q: Tagesziel an Produktivzeit mit Timer. Diese kann durch Arbeiten aufgebaut werden, und zum Spielen verbraucht werden. +A: Ist definitiv sehr sinnvoll. Der zweite Teil würde eher nur bedingt verwendet werden, vom Spielen würde man sich nicht abhalten lassen (Die App kann mich nicht kontrollieren, da bin ich Egoistisch). +Der erste Teil könnte aber zu mehr Motivation führen. Ein tägliches Overview wäre nett. + +Q: Benutzt du die Pomodoro-Technik? +A: Hälte zwar was davon, Nutzt es aber nur bedingt. Es wird sehr prokastiniert. Beim nähern einer Deadline wird sich hingesessen und in langen Sessions von 6-8 Stunden. Da sind kleine Pausen nicht erwünscht/werden ignoriert. + +Q: Tagesziel zum Wassertrinken mit Widget, welches sich schrittweise auffüllen kann. Konfetti/Cheer beim Abschluss. +A: Parallel zum täglichen Overview? Ja, gerne. Führt zu mehr Motivation. + +Q: Wie wichtig wäre dir eine Todo-Liste? (welche Aufgaben und Wann)? +A: Sehr gut, würde bei ungetakteter/unstrukturierter Planung und Proktrastination helfen. Timelines wären cool. +Q: Wie sieht es mit sich wiederholenden Aufgaben aus? +A: Das wäre praktisch. (z.B, für Praktika). + +Q: Wie wichtig ist eine angenehme UX? +A: Am Anfang ist eine einfache Übersicht wichtig. Diese soll aber customizable sein, und dann auch gerne komplizierter werden. + +Q: Fallen dir irgendwelche nice-to-haves ein? +A: Ein Gesundheitstracker mit Schritten und Schlafrhytmus. Kalendar Integration wie beim Google-Kalendar (importieren). "Newsfeed": eine Übersicht von 3 Wichtige Punkte zu aktuellen Themen (wie IT Sicherheit). Ähnlich zu Google Alerts(?). + +Q: Was hältst du vom Buddy-System? +A: Könnte sehr motivierend sein aufgrund des Wettbewerbs, und dem Gefühl von "Ich bin nicht alleine". + + +INTERVIEW END 14:10 diff --git a/docs/Interviews/interview_2_notes.md b/docs/Interviews/interview_2_notes.md new file mode 100644 index 0000000..465353c --- /dev/null +++ b/docs/Interviews/interview_2_notes.md @@ -0,0 +1,62 @@ +Interview Start ~14:35 + +App und basic info erklärt. Fach gefragt. +Studiert Informatik in Richtung KMI, im vierten Semester/ zweiten Fachsemester. + +Q: Welche Produktivitätstools/-apps verwendest du? +A: Notion und den iOS Kalender - allerdings nicht sehr aktiv. + +Q: Was sind Features von Notion, die dir Gefallen? +A: Variabilität. Beispiel sind verschiedene templates, die man miteinander verlinken kann. Die customizability ist nett. + +Q: Wie strukturierst du deinem Alltag? Bist du damit zufrieden? +A: Der Alltag ist nicht sehr strukturiert. Damit bin ich auch nicht zufrieden. + +Q: Suchst du nach neue Apps? +A: Ja. + +Q: Hast du ein System, um Aufgaben zu priorisieren? +A: Ja, aber nur im Kopf. + +Q: Hast du Herausforderungen beim Erledigen von Aufgaben? +A: Weniger, aber die Freundin stört oft die Pläne. Es wäre nett, zusammen planen zu können. (team, kollab?) + +Q: Machst du einige Dinge nicht, weil du einfach unmotiviert bist? +A: Ja. Zu Haushaltsaufgaben habe ich oft null Motivation, und schiebe das teilweise um 1-2 Tage. + +Q: (Buddy system erklärt) Was denkst du vom Buddy System? +A: Ich würde micht eher betreuet und bemuttert als geholfen fühlen. Das wäre eher ärgerlich, und würde der Motivation schaden. Automatisierte Systeme wären vielleicht eine bessere Hilfe bei der Motivation. Beispiele sind Duolingo und die iMessage double notifs. + +Q: (Widget Wand, customization, Direkt Übersicht erklärt). +Q: Was hältst du vom Timer-Widget? Mit den zwei Modi: ich arbeite jetzt / ich belohne mich jetzt (spielen) +A: Ich finde es gut, eine einsichtbare Statistik. (produktivität vs zockzeit). + +Q: Wie wäre ein Übersicht der täglichen Ziele? Recaps? +A: Jup, finde es cool. Ja. (Vergleich mit dem Screentime-Report, aber positiv.) + +Q: Widget idee: Wasserflasche? +A: Ist gut. Es wäre gut, diese Widget auf dem Home-Screen zu haben für die bester Verfügbarkeit. + +Q: Idee: Eine TODO-Liste mit Tasks und einer Timeline: Welche tasks und wann? +A: Dazu habe ich nicht wirklich eine Meinung. + +Q: Wie könnte man diese Liste trotzdem attraktiv gestalten? +A: Aufgabenfreie Zeiten sollten rausgestrichen werden. Es wäre cool, wenn man long term notifs kriegen würde (geburtstag in 5 Tage, etc). Die nächsten aufgaben sollten angezeigt werden, egal, wie weit sie noch entfernt sind. + +Q: Gibt es noch irgendwelche gewünschten Features? +A: Aufgrund von ADHS wäre ein schneller Zugriff auf die Übersicht gut. Da man das oft benutzt, sollte das schnell erreichbar sein. Es sollte viele, penetrante Benachrichtigungen geben. Home screen widgets wären gut. + +Q: Was wäre interessante - Kurzfristige Alltagsorganisation, oder Organisation von langfristigen Projekten? +A: Hauptsächlich Alltagsorganisation, aber langfristige Projekte wären auch cool mit einzuarbeiten. + +A: Eine Spracherkennung wäre auch nett mit Speech to text. Wie Alexa, um neue Aufgaben zu hinzufügen. + +Q: Was hälst du von der Pomodoro-Technik? +A: Das bringt bei mir nichts. Wenn ich im Flow bin, bin ich im Flow bis ich fertig bin. + + +Interview end ~14:50 + + + + diff --git a/docs/assets/extra_styles.css b/docs/assets/extra_styles.css new file mode 100644 index 0000000..3cb89a3 --- /dev/null +++ b/docs/assets/extra_styles.css @@ -0,0 +1,15 @@ +.mobile-frame { + width: 374px; + height: 676px; + padding: 80px 50px 40px 50px; + background-image: url("assets/images/mobile.jpg"); + background-size: cover; + background-repeat: no-repeat; + margin: 1em auto; +} + +.mobile-frame iframe { + width: 95%; + height: 90%; + background-color: darkgray; +} diff --git a/docs/assets/images/DataFlowDiagram.png b/docs/assets/images/DataFlowDiagram.png new file mode 100644 index 0000000..cfcee7c Binary files /dev/null and b/docs/assets/images/DataFlowDiagram.png differ diff --git a/docs/assets/images/FIGMA1.png b/docs/assets/images/FIGMA1.png new file mode 100644 index 0000000..f872dd1 Binary files /dev/null and b/docs/assets/images/FIGMA1.png differ diff --git a/docs/assets/images/FIGMA2.png b/docs/assets/images/FIGMA2.png new file mode 100644 index 0000000..5547aa6 Binary files /dev/null and b/docs/assets/images/FIGMA2.png differ diff --git a/docs/assets/images/FIGMA3.png b/docs/assets/images/FIGMA3.png new file mode 100644 index 0000000..802b062 Binary files /dev/null and b/docs/assets/images/FIGMA3.png differ diff --git a/docs/assets/images/FIGMA4.png b/docs/assets/images/FIGMA4.png new file mode 100644 index 0000000..777d9e5 Binary files /dev/null and b/docs/assets/images/FIGMA4.png differ diff --git a/docs/assets/images/Habitrack_Logo.png b/docs/assets/images/Habitrack_Logo.png new file mode 100644 index 0000000..cd94117 Binary files /dev/null and b/docs/assets/images/Habitrack_Logo.png differ diff --git a/docs/assets/images/UML1.png b/docs/assets/images/UML1.png new file mode 100644 index 0000000..64bbc96 Binary files /dev/null and b/docs/assets/images/UML1.png differ diff --git a/docs/assets/images/UML2.png b/docs/assets/images/UML2.png new file mode 100644 index 0000000..493f66e Binary files /dev/null and b/docs/assets/images/UML2.png differ diff --git a/docs/assets/images/UserFlowDiagram.png b/docs/assets/images/UserFlowDiagram.png new file mode 100644 index 0000000..097709e Binary files /dev/null and b/docs/assets/images/UserFlowDiagram.png differ diff --git a/docs/assets/images/hci_paper_prototype1.jpg b/docs/assets/images/hci_paper_prototype1.jpg new file mode 100644 index 0000000..4dc7b20 Binary files /dev/null and b/docs/assets/images/hci_paper_prototype1.jpg differ diff --git a/docs/assets/images/hci_paper_prototype2.jpg b/docs/assets/images/hci_paper_prototype2.jpg new file mode 100644 index 0000000..40ffd69 Binary files /dev/null and b/docs/assets/images/hci_paper_prototype2.jpg differ diff --git a/docs/assets/images/mobile.jpg b/docs/assets/images/mobile.jpg new file mode 100644 index 0000000..53542f3 Binary files /dev/null and b/docs/assets/images/mobile.jpg differ diff --git a/docs/assets/images/mvp_table.png b/docs/assets/images/mvp_table.png new file mode 100644 index 0000000..cdbbd89 Binary files /dev/null and b/docs/assets/images/mvp_table.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..412c62d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,12 @@ + + +# Project Documentation + + +## Vision + +**For people who want to keep track of their hydration, medicine and tasks, the app offers simple overviews of things that are still undone. Unlike todoist our Product will offer beautiful and accessible gauges and metrics.** + + + +## enjoy :) diff --git a/learning/demo-app/.gitignore b/learning/demo-app/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/learning/demo-app/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/learning/demo-app/.metadata b/learning/demo-app/.metadata new file mode 100644 index 0000000..25d90ec --- /dev/null +++ b/learning/demo-app/.metadata @@ -0,0 +1,30 @@ +# 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. + +version: + revision: 796c8ef79279f9c774545b3771238c3098dbefab + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + - platform: android + create_revision: 796c8ef79279f9c774545b3771238c3098dbefab + base_revision: 796c8ef79279f9c774545b3771238c3098dbefab + + # 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/learning/demo-app/.vscode/settings.json b/learning/demo-app/.vscode/settings.json new file mode 100644 index 0000000..3f8fa42 --- /dev/null +++ b/learning/demo-app/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "spellright.language": ["de", "en"], + "spellright.documentTypes": ["markdown", "latex", "plaintext"] +} diff --git a/learning/demo-app/.vscode/spellright.dict b/learning/demo-app/.vscode/spellright.dict new file mode 100644 index 0000000..a62cef8 --- /dev/null +++ b/learning/demo-app/.vscode/spellright.dict @@ -0,0 +1,4 @@ +riverpod +freezed +equatable +figma diff --git a/learning/demo-app/README.md b/learning/demo-app/README.md new file mode 100644 index 0000000..e10c34f --- /dev/null +++ b/learning/demo-app/README.md @@ -0,0 +1,32 @@ +# HCI/Flutter Demo Project + +A Flutter demo project with basic architecture. + +## Architecture und Libs + +This project uses + +- [flutter_riverpod](https://pub.dev/packages/flutter_riverpod) for state management including [annotation]() and [generator]() +- [go_router](https://pub.dev/packages/go_router) for navigation +- [freezed](https://pub.dev/packages/freezed), [freezed_annotation](https://pub.dev/packages/freezed_annotation) and [equatable](https://pub.dev/packages/equatable) to write less code + + +## Navigation and Layout + +For the purpose of demonstration the app + +- uses bottom navigation or navigation rail depending on the screen +- navigates to a separate screen for the details view +- shows the edit form responsive: either side by side or below + +## Debug +To debug this application you need an installation of Flutter and Android +1. open the folder with VS Code +2. open the terminal and run `flutter pub get` +3. open any dart file in the folder lib and hit F5 + +If you have problems to start the application run in the terminal `flutter doctor`. + +## Edit +If you edit any provider or freezed class make sure the builder is running +`dart run build_runner watch` \ No newline at end of file diff --git a/learning/demo-app/analysis_options.yaml b/learning/demo-app/analysis_options.yaml new file mode 100644 index 0000000..b07876a --- /dev/null +++ b/learning/demo-app/analysis_options.yaml @@ -0,0 +1,41 @@ +# 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 +include: package:very_good_analysis/analysis_options.yaml + +analyzer: + exclude: + - "**.g.dart" + - "**.freezed.dart" + errors: + invalid_annotation_target: ignore + +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-lang.github.io/linter/lints/index.html. + # + # 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: + # a must for libs, but within a small app it is ok to leave it to the end + # and document important stuff only + public_member_api_docs: false + # I prefere relative imports within the app + always_use_package_imports: false + # things that belong together are easyer to find + sort_pub_dependencies: false + # if you learn a new framework, I think it is better to use and see the types + omit_local_variable_types: false diff --git a/learning/demo-app/android/.gitignore b/learning/demo-app/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/learning/demo-app/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/learning/demo-app/android/app/build.gradle b/learning/demo-app/android/app/build.gradle new file mode 100644 index 0000000..4df5b91 --- /dev/null +++ b/learning/demo-app/android/app/build.gradle @@ -0,0 +1,67 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +android { + namespace "com.example.ri_go_demo" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.ri_go_demo" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies {} diff --git a/learning/demo-app/android/app/src/debug/AndroidManifest.xml b/learning/demo-app/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/learning/demo-app/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/learning/demo-app/android/app/src/main/AndroidManifest.xml b/learning/demo-app/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..42e74ff --- /dev/null +++ b/learning/demo-app/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/learning/demo-app/android/app/src/main/kotlin/com/example/app/MainActivity.kt b/learning/demo-app/android/app/src/main/kotlin/com/example/app/MainActivity.kt new file mode 100644 index 0000000..461fcfb --- /dev/null +++ b/learning/demo-app/android/app/src/main/kotlin/com/example/app/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/learning/demo-app/android/app/src/main/kotlin/com/example/ri_go_demo/MainActivity.kt b/learning/demo-app/android/app/src/main/kotlin/com/example/ri_go_demo/MainActivity.kt new file mode 100644 index 0000000..79c0ede --- /dev/null +++ b/learning/demo-app/android/app/src/main/kotlin/com/example/ri_go_demo/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.ri_go_demo + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/learning/demo-app/android/app/src/main/res/drawable-v21/launch_background.xml b/learning/demo-app/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/learning/demo-app/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/learning/demo-app/android/app/src/main/res/drawable/launch_background.xml b/learning/demo-app/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/learning/demo-app/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/learning/demo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/learning/demo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/learning/demo-app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/learning/demo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/learning/demo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/learning/demo-app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/learning/demo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/learning/demo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/learning/demo-app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/learning/demo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/learning/demo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/learning/demo-app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/learning/demo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/learning/demo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/learning/demo-app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/learning/demo-app/android/app/src/main/res/values-night/styles.xml b/learning/demo-app/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/learning/demo-app/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/learning/demo-app/android/app/src/main/res/values/styles.xml b/learning/demo-app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/learning/demo-app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/learning/demo-app/android/app/src/profile/AndroidManifest.xml b/learning/demo-app/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/learning/demo-app/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/learning/demo-app/android/build.gradle b/learning/demo-app/android/build.gradle new file mode 100644 index 0000000..bc157bd --- /dev/null +++ b/learning/demo-app/android/build.gradle @@ -0,0 +1,18 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/learning/demo-app/android/gradle.properties b/learning/demo-app/android/gradle.properties new file mode 100644 index 0000000..598d13f --- /dev/null +++ b/learning/demo-app/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx4G +android.useAndroidX=true +android.enableJetifier=true diff --git a/learning/demo-app/android/gradle/wrapper/gradle-wrapper.properties b/learning/demo-app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e1ca574 --- /dev/null +++ b/learning/demo-app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/learning/demo-app/android/settings.gradle b/learning/demo-app/android/settings.gradle new file mode 100644 index 0000000..1d6d19b --- /dev/null +++ b/learning/demo-app/android/settings.gradle @@ -0,0 +1,26 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() + + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + id "org.jetbrains.kotlin.android" version "1.7.10" apply false +} + +include ":app" diff --git a/learning/demo-app/ios/.gitignore b/learning/demo-app/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/ios/Flutter/AppFrameworkInfo.plist b/learning/demo-app/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/learning/demo-app/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 + 12.0 + + diff --git a/learning/demo-app/ios/Flutter/Debug.xcconfig b/learning/demo-app/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/learning/demo-app/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/learning/demo-app/ios/Flutter/Release.xcconfig b/learning/demo-app/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/learning/demo-app/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/learning/demo-app/ios/Runner.xcodeproj/project.pbxproj b/learning/demo-app/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..06f38e3 --- /dev/null +++ b/learning/demo-app/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 = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.ri_go_demo; + 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.ri_go_demo.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.ri_go_demo.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.ri_go_demo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.ri_go_demo; + 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.ri_go_demo; + 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/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/learning/demo-app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/learning/demo-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/learning/demo-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..8e3ca5d --- /dev/null +++ b/learning/demo-app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/learning/demo-app/ios/Runner.xcworkspace/contents.xcworkspacedata b/learning/demo-app/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/learning/demo-app/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/learning/demo-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/learning/demo-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/learning/demo-app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/learning/demo-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/learning/demo-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/learning/demo-app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/learning/demo-app/ios/Runner/AppDelegate.swift b/learning/demo-app/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/learning/demo-app/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/learning/demo-app/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/learning/demo-app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/learning/demo-app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/learning/demo-app/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/learning/demo-app/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/learning/demo-app/ios/Runner/Base.lproj/Main.storyboard b/learning/demo-app/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/learning/demo-app/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/learning/demo-app/ios/Runner/Info.plist b/learning/demo-app/ios/Runner/Info.plist new file mode 100644 index 0000000..ace7eeb --- /dev/null +++ b/learning/demo-app/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Ri Go Demo + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + ri_go_demo + 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 + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/learning/demo-app/ios/Runner/Runner-Bridging-Header.h b/learning/demo-app/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/learning/demo-app/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/learning/demo-app/ios/RunnerTests/RunnerTests.swift b/learning/demo-app/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/l10n.yaml b/learning/demo-app/l10n.yaml new file mode 100644 index 0000000..d39f6e7 --- /dev/null +++ b/learning/demo-app/l10n.yaml @@ -0,0 +1,5 @@ +arb-dir: lib/l10n +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart +untranslated-messages-file: untranslated_messages.json +nullable-getter: false diff --git a/learning/demo-app/lib/l10n/app_de.arb b/learning/demo-app/lib/l10n/app_de.arb new file mode 100644 index 0000000..16fbdd3 --- /dev/null +++ b/learning/demo-app/lib/l10n/app_de.arb @@ -0,0 +1,20 @@ +{ + "@@locale": "de", + "appTitle": "Demo-App mit Riverpod und go_router", + "ok": "Ok", + "cancel": "Abbrechen", + "yes": "Ja", + "no": "Nein", + "add": "Hinzufügen", + "save": "Speichern", + "error": "Sorry, da ist etwas schief gegangen...", + "loading": "Lade ...", + "select": "Auswahl", + "selected": "Ausgewählt", + "search": "Suche", + "counterLabel": "Du hast den Button sooft gedrückt:", + "fabMainTooltip": "erhöhen", + "placeholder": "placeholder", + "home": "Start", + "counter": "Zähler" +} \ No newline at end of file diff --git a/learning/demo-app/lib/l10n/app_en.arb b/learning/demo-app/lib/l10n/app_en.arb new file mode 100644 index 0000000..63ad825 --- /dev/null +++ b/learning/demo-app/lib/l10n/app_en.arb @@ -0,0 +1,27 @@ +{ + "@@locale": "en", + "appTitle": "Demo app with riverpod and go_router", + "ok": "Ok", + "cancel": "Cancel", + "yes": "Yes", + "no": "No", + "add": "Add", + "save": "Save", + "error": "We apologize for the inconvenience. This should not have happened...", + "loading": "Loading", + "select": "Select", + "selected": "Selected", + "deselect": "Deselect", + "search": "Search", + "counterLabel": "You have pushed the button this many times:", + "fabMainTooltip": "Increment", + "placeholder": "placeholder", + "requiredField": "This field is required", + "labelName": "Name", + "labelImageUrl": "Url of an image", + "delete": "Delete", + "home": "Home", + "counter": "Counter", + "details": "Details", + "noItems": "Sorry, no items." +} \ No newline at end of file diff --git a/learning/demo-app/lib/main.dart b/learning/demo-app/lib/main.dart new file mode 100644 index 0000000..926373e --- /dev/null +++ b/learning/demo-app/lib/main.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'src/routing/app_router.dart'; +import 'src/utils/localization.dart'; + +Future main() async { + runApp(const ProviderScope(child: MyApp())); +} + +class MyApp extends ConsumerWidget { + const MyApp({super.key}); + @override + Widget build(BuildContext context, WidgetRef ref) { + final goRouter = ref.watch(goRouterProvider); + return MaterialApp.router( + routerConfig: goRouter, + debugShowCheckedModeBanner: false, + onGenerateTitle: (context) => context.loc.appTitle, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.green), + useMaterial3: true, + ), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + // use to find missing semantics + // showSemanticsDebugger: true, + ); + } +} diff --git a/learning/demo-app/lib/src/common_widgets/async_value_widget.dart b/learning/demo-app/lib/src/common_widgets/async_value_widget.dart new file mode 100644 index 0000000..7e1bcfb --- /dev/null +++ b/learning/demo-app/lib/src/common_widgets/async_value_widget.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'error_message_widget.dart'; + +//credits to Code with Andrea https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/src/common_widgets/async_value_widget.dart +class AsyncValueWidget extends StatelessWidget { + const AsyncValueWidget({required this.value, required this.data, super.key}); + final AsyncValue value; + final Widget Function(T) data; + + @override + Widget build(BuildContext context) { + return value.when( + data: data, + error: (error, st) => Center(child: ErrorMessageWidget(error)), + loading: () => const SizedBox( + width: 60, + height: 60, + child: Center(child: CircularProgressIndicator()), + ), + ); + } +} diff --git a/learning/demo-app/lib/src/common_widgets/error_message_widget.dart b/learning/demo-app/lib/src/common_widgets/error_message_widget.dart new file mode 100644 index 0000000..0a00635 --- /dev/null +++ b/learning/demo-app/lib/src/common_widgets/error_message_widget.dart @@ -0,0 +1,55 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:ri_go_demo/src/exceptions/api_exception.dart'; + +import '../utils/localization.dart'; +import '../utils/logger.dart'; + +//credits to Code with Andrea https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/src/common_widgets/error_message_widget.dart + +/// Simple reusable widget to show errors to the user. +class ErrorMessageWidget extends StatelessWidget { + const ErrorMessageWidget(this.error, {super.key}); + + /// Error object, might be a DioException. + final Object? error; + @override + Widget build(BuildContext context) { + return Text( + _pimpError(error, context.loc.error), + style: + Theme.of(context).textTheme.titleLarge!.copyWith(color: Colors.red), + ); + } + + String _pimpError(Object? error, String defaultStr) { + if (error == null) { + logger.d('ErrorMessageWidget - _pimpError - no error'); + return defaultStr; + } + try { + final dioEx = error as DioException; + if (dioEx.response != null) { + final map = dioEx.response!.data as Map; + if (map.containsKey('detail')) { + return map['detail']! as String; + } + } + } catch (ex) { + logger.e( + 'ErrorMessageWidget - _pimpError - could not extract info', + error: ex, + ); + } + try { + final apiException = error as ApiException; + return 'status ${apiException.statusCode}: ${apiException.message}'; + } catch (ex) { + logger.e( + 'ErrorMessageWidget - _pimpError - could not extract message', + error: ex, + ); + } + return defaultStr; + } +} diff --git a/learning/demo-app/lib/src/common_widgets/form_field_widget.dart b/learning/demo-app/lib/src/common_widgets/form_field_widget.dart new file mode 100644 index 0000000..0b258a4 --- /dev/null +++ b/learning/demo-app/lib/src/common_widgets/form_field_widget.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +//similar from code with Andrea +class FormFieldWidget extends StatelessWidget { + const FormFieldWidget({ + required this.controller, + required this.labelText, + super.key, + this.keyboardType, + this.formFieldKey, + this.required = false, + this.initialValue = '', + }); + + final TextEditingController controller; + final String labelText; + final TextInputType? keyboardType; + final bool required; + + final Key? formFieldKey; + + final String initialValue; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Semantics( + label: labelText, + child: TextFormField( + key: formFieldKey, + controller: controller, + decoration: InputDecoration( + labelText: labelText, + ), + autocorrect: false, + textInputAction: TextInputAction.next, + keyboardType: keyboardType, + validator: (value) { + if (required && (value == null || value.isEmpty)) { + return ''; //context.loc.requiredField; + } + return null; + }, + ), + ), + ], + ); + } +} diff --git a/learning/demo-app/lib/src/common_widgets/primary_button.dart b/learning/demo-app/lib/src/common_widgets/primary_button.dart new file mode 100644 index 0000000..b7f3e7a --- /dev/null +++ b/learning/demo-app/lib/src/common_widgets/primary_button.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +// credits to Code with Andrea, https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/src/common_widgets/primary_button.dart + +class PrimaryButton extends StatelessWidget { + const PrimaryButton({ + required this.label, + required this.onPressed, + super.key, + this.isEnabled = true, + this.backgroundColor, + this.foregroundColor, + }); + final VoidCallback? onPressed; + final String label; + final bool isEnabled; + final Color? backgroundColor; + final Color? foregroundColor; + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: const EdgeInsets.all(12), + child: SizedBox( + height: 48, + child: ElevatedButton( + onPressed: isEnabled ? onPressed : null, + style: ElevatedButton.styleFrom( + backgroundColor: + backgroundColor ?? Theme.of(context).colorScheme.primary, + foregroundColor: + foregroundColor ?? Theme.of(context).colorScheme.onPrimary, + ), + child: Text( + label, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + ), + ), + ), + ); + } +} diff --git a/learning/demo-app/lib/src/common_widgets/two_panel_widget.dart b/learning/demo-app/lib/src/common_widgets/two_panel_widget.dart new file mode 100644 index 0000000..7e09a9b --- /dev/null +++ b/learning/demo-app/lib/src/common_widgets/two_panel_widget.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +import '../constants/breakpoint.dart'; + +class TwoPanelWidget extends StatelessWidget { + const TwoPanelWidget({ + required this.firstPanel, + required this.secondPanel, + super.key, + }); + final Widget firstPanel; + final Widget secondPanel; + @override + Widget build(BuildContext context) { + final screenSize = MediaQuery.of(context).size; + final isWideScreen = screenSize.width > Breakpoint.tablet; + + if (isWideScreen) { + // Display master and detail side by side + return Row( + children: [ + Expanded( + flex: 2, + child: firstPanel, + ), + Expanded( + flex: 2, + child: secondPanel, + ), + ], + ); + } else { + // Display master and detail vertically + return Column( + children: [ + Expanded( + flex: 2, + child: firstPanel, + ), + Expanded( + flex: 2, + child: secondPanel, + ), + ], // Display the first item initially + ); + } + } +} diff --git a/learning/demo-app/lib/src/constants/api.dart b/learning/demo-app/lib/src/constants/api.dart new file mode 100644 index 0000000..e7a1e4c --- /dev/null +++ b/learning/demo-app/lib/src/constants/api.dart @@ -0,0 +1,6 @@ +// https://api-generator.retool.com/grQMFP/crud-demo/ +abstract class Api { + static const String schema = 'https'; + static const String host = 'api-generator.retool.com'; + static const String path = 'grQMFP/crud-demo/'; +} diff --git a/learning/demo-app/lib/src/constants/breakpoint.dart b/learning/demo-app/lib/src/constants/breakpoint.dart new file mode 100644 index 0000000..e422eaa --- /dev/null +++ b/learning/demo-app/lib/src/constants/breakpoint.dart @@ -0,0 +1,3 @@ +class Breakpoint { + static const double tablet = 600; +} diff --git a/learning/demo-app/lib/src/constants/ui_constants.dart b/learning/demo-app/lib/src/constants/ui_constants.dart new file mode 100644 index 0000000..caad60b --- /dev/null +++ b/learning/demo-app/lib/src/constants/ui_constants.dart @@ -0,0 +1,5 @@ +abstract class UIConstants { + static const double minHitTargetHeight = 55; + static const double verticalItemSpace = 8; + static const double defaultPadding = 12; +} diff --git a/learning/demo-app/lib/src/exceptions/api_exception.dart b/learning/demo-app/lib/src/exceptions/api_exception.dart new file mode 100644 index 0000000..97b38a2 --- /dev/null +++ b/learning/demo-app/lib/src/exceptions/api_exception.dart @@ -0,0 +1,8 @@ +class ApiException implements Exception { + ApiException(this.statusCode, this.message); + final int statusCode; + final String message; + + @override + String toString() => message; +} diff --git a/learning/demo-app/lib/src/features/counter/presentation/counter_screen.dart b/learning/demo-app/lib/src/features/counter/presentation/counter_screen.dart new file mode 100644 index 0000000..16628ec --- /dev/null +++ b/learning/demo-app/lib/src/features/counter/presentation/counter_screen.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import '../../../utils/localization.dart'; +import 'counter_screen_controller.dart'; + +class CounterScreen extends ConsumerWidget { + const CounterScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(counterProvider); + final controller = ref.read(counterProvider.notifier); + + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(context.loc.appTitle), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + context.loc.counterLabel, + ), + Text( + '$state', + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: controller.increment, + tooltip: context.loc.fabMainTooltip, + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/learning/demo-app/lib/src/features/counter/presentation/counter_screen_controller.dart b/learning/demo-app/lib/src/features/counter/presentation/counter_screen_controller.dart new file mode 100644 index 0000000..dc6b336 --- /dev/null +++ b/learning/demo-app/lib/src/features/counter/presentation/counter_screen_controller.dart @@ -0,0 +1,25 @@ + +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'counter_screen_controller.g.dart'; + + +/// Annotating a class by `@riverpod` defines a new shared +/// state for your application, +/// accessible using the generated [counterProvider]. +/// This class is both responsible for initializing the state (through the +/// [build] method) +/// and exposing ways to modify it (cf [increment]). +@riverpod +class Counter extends _$Counter { + /// Classes annotated by `@riverpod` **must** define a [build] function. + /// This function is expected to return the initial state of your + /// shared state. + /// It is totally acceptable for this function to return a [Future] or + /// [Stream] if you need to. + /// You can also freely define parameters on this method. + @override + int build() => 0; + + void increment() => state++; +} diff --git a/learning/demo-app/lib/src/features/counter/presentation/counter_screen_controller.g.dart b/learning/demo-app/lib/src/features/counter/presentation/counter_screen_controller.g.dart new file mode 100644 index 0000000..614b604 --- /dev/null +++ b/learning/demo-app/lib/src/features/counter/presentation/counter_screen_controller.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'counter_screen_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$counterHash() => r'4243b34530f53accfd9014a9f0e316fe304ada3e'; + +/// Annotating a class by `@riverpod` defines a new shared +/// state for your application, +/// accessible using the generated [counterProvider]. +/// This class is both responsible for initializing the state (through the +/// [build] method) +/// and exposing ways to modify it (cf [increment]). +/// +/// Copied from [Counter]. +@ProviderFor(Counter) +final counterProvider = AutoDisposeNotifierProvider.internal( + Counter.new, + name: r'counterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$counterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$Counter = AutoDisposeNotifier; +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/data/people_repository.dart b/learning/demo-app/lib/src/features/rest_crud_demo/data/people_repository.dart new file mode 100644 index 0000000..69633b7 --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/data/people_repository.dart @@ -0,0 +1,124 @@ +import 'dart:convert'; + +import 'package:dio/dio.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../../../constants/api.dart'; +import '../../../exceptions/api_exception.dart'; +import '../../../utils/dio_provider.dart'; +import '../../../utils/logger.dart'; +import '../domain/person.dart'; + +part 'people_repository.g.dart'; + +class PeopleRepository { + PeopleRepository({required this.dio}); + final Dio dio; + + String _getUrl({int? id}) { + final url = + Uri(scheme: Api.schema, host: Api.host, path: Api.path).toString(); + if (id != null) { + return '$url$id'; + } else { + return url; + } + } + + Future getPersonById({required int id}) async { + final url = _getUrl(id: id); + // ignore: inference_failure_on_function_invocation + final response = await dio.get(url); + if (response.statusCode == 200 && response.data != null) { + final person = + Person.fromJson(json.decode(response.data!) as Map); + return person; + } else { + throw ApiException( + response.statusCode ?? -1, + 'getPersonById ${response.statusCode}, data=${response.data}', + ); + } + } + + Future> getPeople() async { + logger.d('people_repository.getPeople'); + final url = _getUrl(); + final response = await dio.get>(url); + if (response.statusCode == 200 && response.data != null) { + final dataList = response.data!; + return dataList + .map( + (personJson) => Person.fromJson(personJson as Map), + ) + .toList(); + } else { + throw ApiException( + response.statusCode ?? -1, + 'getPeople ${response.statusCode}, data=${response.data}', + ); + } + } + + Future updatePerson({required Person person}) async { + final url = _getUrl(id: person.id); + final response = await dio.put(url, data: person.toJson()); + if (response.statusCode == 200 && response.data != null) { + final personUpdated = + Person.fromJson(json.decode(response.data!) as Map); + return personUpdated; + } else { + throw ApiException( + response.statusCode ?? -1, + 'updateOwner ${response.statusCode}, data=${response.data}', + ); + } + } + + Future deletePerson(int id) async { + final url = _getUrl(id: id); + final response = await dio.delete(url); + if (response.statusCode == 200) { + return true; + } else { + throw ApiException( + response.statusCode ?? -1, + 'deletePerson ${response.statusCode}, data=${response.data}', + ); + } + } + + Future savePerson({required Person person}) async { + final url = _getUrl(); + // this api uses the id if it exists, hence in case of a post + // we make sure, there is no id + final response = + await dio.post(url, data: person.toJsonWithoutId()); + if (response.statusCode == 201 && response.data != null) { + final newPerson = + Person.fromJson(json.decode(response.data!) as Map); + return newPerson; + } else { + throw ApiException( + response.statusCode ?? -1, + 'savePerson ${response.statusCode}, data=${response.data}', + ); + } + } +} + +@riverpod +PeopleRepository peopleRepository(PeopleRepositoryRef ref) => + PeopleRepository(dio: ref.read(dioProvider)); + +@riverpod +Future> fetchPeople(FetchPeopleRef ref) async { + logger.d('people_repository.fetchPeople'); + final repo = ref.read(peopleRepositoryProvider); + return repo.getPeople(); +} + +@riverpod +Future fetchPersonById(FetchPersonByIdRef ref, int id) async { + final repo = ref.read(peopleRepositoryProvider); + return repo.getPersonById(id: id); +} diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/data/people_repository.g.dart b/learning/demo-app/lib/src/features/rest_crud_demo/data/people_repository.g.dart new file mode 100644 index 0000000..0c2c0b7 --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/data/people_repository.g.dart @@ -0,0 +1,141 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'people_repository.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$peopleRepositoryHash() => r'd0e91e6cbf45120cbcd670b6cc99fe71a9d76429'; + +/// See also [peopleRepository]. +@ProviderFor(peopleRepository) +final peopleRepositoryProvider = AutoDisposeProvider.internal( + peopleRepository, + name: r'peopleRepositoryProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$peopleRepositoryHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef PeopleRepositoryRef = AutoDisposeProviderRef; +String _$fetchPeopleHash() => r'772e01d6c483daa24de83820a021601fa93618e7'; + +/// See also [fetchPeople]. +@ProviderFor(fetchPeople) +final fetchPeopleProvider = AutoDisposeFutureProvider>.internal( + fetchPeople, + name: r'fetchPeopleProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$fetchPeopleHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef FetchPeopleRef = AutoDisposeFutureProviderRef>; +String _$fetchPersonByIdHash() => r'ab1560261f3491819dc88719e855f1c9b973ed21'; + +/// Copied from Dart SDK +class _SystemHash { + _SystemHash._(); + + static int combine(int hash, int value) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + value); + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); + } + + static int finish(int hash) { + // ignore: parameter_assignments + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + // ignore: parameter_assignments + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); + } +} + +typedef FetchPersonByIdRef = AutoDisposeFutureProviderRef; + +/// See also [fetchPersonById]. +@ProviderFor(fetchPersonById) +const fetchPersonByIdProvider = FetchPersonByIdFamily(); + +/// See also [fetchPersonById]. +class FetchPersonByIdFamily extends Family> { + /// See also [fetchPersonById]. + const FetchPersonByIdFamily(); + + /// See also [fetchPersonById]. + FetchPersonByIdProvider call( + int id, + ) { + return FetchPersonByIdProvider( + id, + ); + } + + @override + FetchPersonByIdProvider getProviderOverride( + covariant FetchPersonByIdProvider provider, + ) { + return call( + provider.id, + ); + } + + static const Iterable? _dependencies = null; + + @override + Iterable? get dependencies => _dependencies; + + static const Iterable? _allTransitiveDependencies = null; + + @override + Iterable? get allTransitiveDependencies => + _allTransitiveDependencies; + + @override + String? get name => r'fetchPersonByIdProvider'; +} + +/// See also [fetchPersonById]. +class FetchPersonByIdProvider extends AutoDisposeFutureProvider { + /// See also [fetchPersonById]. + FetchPersonByIdProvider( + this.id, + ) : super.internal( + (ref) => fetchPersonById( + ref, + id, + ), + from: fetchPersonByIdProvider, + name: r'fetchPersonByIdProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') + ? null + : _$fetchPersonByIdHash, + dependencies: FetchPersonByIdFamily._dependencies, + allTransitiveDependencies: + FetchPersonByIdFamily._allTransitiveDependencies, + ); + + final int id; + + @override + bool operator ==(Object other) { + return other is FetchPersonByIdProvider && other.id == id; + } + + @override + int get hashCode { + var hash = _SystemHash.combine(0, runtimeType.hashCode); + hash = _SystemHash.combine(hash, id.hashCode); + + return _SystemHash.finish(hash); + } +} +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.dart b/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.dart new file mode 100644 index 0000000..9db9dcd --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.dart @@ -0,0 +1,37 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'person.freezed.dart'; +part 'person.g.dart'; + +@freezed +class Person with _$Person { + const factory Person({ + required int id, + required String name, + required String imageUrl, + }) = _Person; + + factory Person.fromJson(Map json) => _$PersonFromJson(json); +} + +extension JsonWithoutId on Person { + String toJsonWithoutId() { + final map = toJson(); + // ignore: cascade_invocations //remove returns the removed field!, cascade_invocations + map.remove('id'); + return json.encode(map); + } +} + +// String toJsonWithoutId(Person p) { +// var a = p.toJson(); +// var b = a.remove('id'); +// var c = json.encode(a); + +// // final map = p.toJson().remove('id'); +// // return json.encode(map); +// return c; +// } diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.freezed.dart b/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.freezed.dart new file mode 100644 index 0000000..3628ae1 --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.freezed.dart @@ -0,0 +1,192 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'person.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +Person _$PersonFromJson(Map json) { + return _Person.fromJson(json); +} + +/// @nodoc +mixin _$Person { + int get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get imageUrl => throw _privateConstructorUsedError; + + Map toJson() => throw _privateConstructorUsedError; + @JsonKey(ignore: true) + $PersonCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PersonCopyWith<$Res> { + factory $PersonCopyWith(Person value, $Res Function(Person) then) = + _$PersonCopyWithImpl<$Res, Person>; + @useResult + $Res call({int id, String name, String imageUrl}); +} + +/// @nodoc +class _$PersonCopyWithImpl<$Res, $Val extends Person> + implements $PersonCopyWith<$Res> { + _$PersonCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? imageUrl = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$_PersonCopyWith<$Res> implements $PersonCopyWith<$Res> { + factory _$$_PersonCopyWith(_$_Person value, $Res Function(_$_Person) then) = + __$$_PersonCopyWithImpl<$Res>; + @override + @useResult + $Res call({int id, String name, String imageUrl}); +} + +/// @nodoc +class __$$_PersonCopyWithImpl<$Res> + extends _$PersonCopyWithImpl<$Res, _$_Person> + implements _$$_PersonCopyWith<$Res> { + __$$_PersonCopyWithImpl(_$_Person _value, $Res Function(_$_Person) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? name = null, + Object? imageUrl = null, + }) { + return _then(_$_Person( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as int, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$_Person with DiagnosticableTreeMixin implements _Person { + const _$_Person( + {required this.id, required this.name, required this.imageUrl}); + + factory _$_Person.fromJson(Map json) => + _$$_PersonFromJson(json); + + @override + final int id; + @override + final String name; + @override + final String imageUrl; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'Person(id: $id, name: $name, imageUrl: $imageUrl)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'Person')) + ..add(DiagnosticsProperty('id', id)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('imageUrl', imageUrl)); + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_Person && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); + } + + @JsonKey(ignore: true) + @override + int get hashCode => Object.hash(runtimeType, id, name, imageUrl); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_PersonCopyWith<_$_Person> get copyWith => + __$$_PersonCopyWithImpl<_$_Person>(this, _$identity); + + @override + Map toJson() { + return _$$_PersonToJson( + this, + ); + } +} + +abstract class _Person implements Person { + const factory _Person( + {required final int id, + required final String name, + required final String imageUrl}) = _$_Person; + + factory _Person.fromJson(Map json) = _$_Person.fromJson; + + @override + int get id; + @override + String get name; + @override + String get imageUrl; + @override + @JsonKey(ignore: true) + _$$_PersonCopyWith<_$_Person> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.g.dart b/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.g.dart new file mode 100644 index 0000000..5de813a --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/domain/person.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'person.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$_Person _$$_PersonFromJson(Map json) => _$_Person( + id: json['id'] as int, + name: json['name'] as String, + imageUrl: json['imageUrl'] as String, + ); + +Map _$$_PersonToJson(_$_Person instance) => { + 'id': instance.id, + 'name': instance.name, + 'imageUrl': instance.imageUrl, + }; diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/presentation/details_screen.dart b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/details_screen.dart new file mode 100644 index 0000000..26af43b --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/details_screen.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gap/gap.dart'; + +import '../../../common_widgets/async_value_widget.dart'; +import '../../../constants/ui_constants.dart'; +import '../../../utils/localization.dart'; +import '../../../utils/logger.dart'; +import '../data/people_repository.dart'; +import '../domain/person.dart'; + +class DetailsScreen extends ConsumerWidget { + const DetailsScreen({super.key, this.id, this.person}); + final int? id; + final Person? person; + + @override + Widget build(BuildContext context, WidgetRef ref) { + if (id == null) { + return Scaffold( + appBar: AppBar( + title: Text(context.loc.details), + ), + body: Text(context.loc.error), + ); + } else if (person != null) { + return DetailsScreenPlainWidget(person!); + } else { + // e.g. navigated to details by bookmark + return AsyncValueWidget( + value: ref.watch(fetchPersonByIdProvider(id!)), + data: DetailsScreenPlainWidget.new, + ); + } + } +} + +class DetailsScreenPlainWidget extends StatelessWidget { + const DetailsScreenPlainWidget(this.person, {super.key}); + final Person person; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(context.loc.details), + ), + body: Padding( + padding: const EdgeInsets.all(UIConstants.defaultPadding), + child: Column( + children: [ + Text('hi ${person.name}'), + const Gap(UIConstants.verticalItemSpace), + if (person.imageUrl.startsWith('http')) + Expanded( + child: Image( + image: NetworkImage(person.imageUrl), + errorBuilder: (context, error, stackTrace) { + logger.d( + 'DetailsScreen - image, url=${person.imageUrl}', + error: error, + stackTrace: stackTrace, + ); + return Text(person.imageUrl); + }, + ), + ) + else + Text(person.imageUrl), + ], + ), + ), + ); + } +} diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_controller.dart b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_controller.dart new file mode 100644 index 0000000..b89c326 --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_controller.dart @@ -0,0 +1,81 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../../../utils/logger.dart'; +import '../data/people_repository.dart'; +import '../domain/person.dart'; + +part 'edit_person_controller.g.dart'; + +// in some cases it might be good to store the current selected item in a +// separate provider +// final currentPersonProvider = StateProvider((ref) { +// return null; +// }); + +@riverpod +class EditPersonController extends _$EditPersonController { + @override + FutureOr build() { + ref.onDispose( + () => logger.i('EditPersonController ----- dispose controller -----'), + ); + state = const AsyncData(null); + return state.value; + } + + //for non string-based setters and hence input fields without own controllers + //use setters like + // void setCheckboxField(bool value) { + // state = AsyncData(state.value!.copyWith(isBossy: value)); + // } + + /// Show the form with the values given by [person]. + void editPerson(Person person) { + state = AsyncData(person); + } + + /// Show the form with empty fields, ready to create a new person. + void newPerson() { + state = const AsyncData(Person(id: -1, name: '', imageUrl: '')); + } + + Future delete() async { + if (state.value == null || state.value!.id < 0) { + return; + } + state = const AsyncLoading(); + try { + final repo = ref.read(peopleRepositoryProvider); + await repo.deletePerson(state.value!.id); + //this provider does not know this change, hence we need to force a + //refresh by invalidating it + ref.invalidate(fetchPeopleProvider); + state = const AsyncData(null); + } catch (error) { + state = AsyncError(error, StackTrace.current); + } + } + + Future save({required String name, required String imageUrl}) async { + if (name.isEmpty || state.value == null) { + return; + } + // use copywith to keep id + final editedPerson = state.value!.copyWith(name: name, imageUrl: imageUrl); + // to keep changes of the form fields, even if we may have an error + state = AsyncData(editedPerson); + state = const AsyncLoading(); + try { + final repo = ref.read(peopleRepositoryProvider); + if (editedPerson.id < 0) { + await repo.savePerson(person: editedPerson); + } else { + await repo.updatePerson(person: editedPerson); + } + ref.invalidate(fetchPeopleProvider); + state = const AsyncData(null); + } catch (error) { + state = AsyncError(error, StackTrace.current); + } + } +} diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_controller.g.dart b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_controller.g.dart new file mode 100644 index 0000000..a2ca7e8 --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_controller.g.dart @@ -0,0 +1,26 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'edit_person_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$editPersonControllerHash() => + r'7c656c78c7d8c60e746ec6bd7ad4e089f2f3fcac'; + +/// See also [EditPersonController]. +@ProviderFor(EditPersonController) +final editPersonControllerProvider = + AutoDisposeAsyncNotifierProvider.internal( + EditPersonController.new, + name: r'editPersonControllerProvider', + debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product') + ? null + : _$editPersonControllerHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef _$EditPersonController = AutoDisposeAsyncNotifier; +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_form.dart b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_form.dart new file mode 100644 index 0000000..af4cc72 --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/edit_person_form.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gap/gap.dart'; + +import '../../../common_widgets/error_message_widget.dart'; +import '../../../common_widgets/form_field_widget.dart'; +import '../../../common_widgets/primary_button.dart'; +import '../../../constants/ui_constants.dart'; +import '../../../utils/localization.dart'; +import 'edit_person_controller.dart'; + +class EditPersonForm extends ConsumerStatefulWidget { + const EditPersonForm({super.key}); + + @override + ConsumerState createState() => _EditPersonFormState(); +} + +class _EditPersonFormState extends ConsumerState { + final _formKey = GlobalKey(); + static const Key _nameKey = Key('name'); + static const Key _imageUrlKey = Key('image_url'); + final _nameController = TextEditingController(); + final _imageUrlController = TextEditingController(); + late final EditPersonController _controller; + + @override + void initState() { + super.initState(); + _controller = ref.read(editPersonControllerProvider.notifier); + } + + @override + void dispose() { + _nameController.dispose(); + _imageUrlController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final state = ref.watch(editPersonControllerProvider); + if (state.isLoading) { + return const CircularProgressIndicator(); + } + if (state.value == null) { + return Container(); + } else { + _nameController.text = state.value!.name; + _imageUrlController.text = state.value!.imageUrl; + return Padding( + padding: const EdgeInsets.all(UIConstants.defaultPadding), + child: Form( + key: _formKey, + child: Column( + children: [ + //put here to have both visible, the error and the form + if (state.hasError) ErrorMessageWidget(state.error), + FormFieldWidget( + formFieldKey: _nameKey, + controller: _nameController, + labelText: context.loc.labelName, + required: true, + ), + const Gap(UIConstants.verticalItemSpace), + FormFieldWidget( + formFieldKey: _imageUrlKey, + controller: _imageUrlController, + labelText: context.loc.labelImageUrl, + ), + const Gap(UIConstants.verticalItemSpace), + Row( + children: [ + PrimaryButton( + label: context.loc.delete, + backgroundColor: Theme.of(context).colorScheme.error, + foregroundColor: Theme.of(context).colorScheme.onError, + onPressed: + state.isLoading ? null : () => {_controller.delete()}, + isEnabled: state.value!.id > 0, + ), + Expanded( + child: PrimaryButton( + label: context.loc.save, + onPressed: state.isLoading + ? null + : () => { + if (_formKey.currentState!.validate()) + { + _controller.save( + name: _nameController.text, + imageUrl: _imageUrlController.text, + ), + }, + }, + ), + ), + ], + ), + ], + ), + ), + ); + } + } +} diff --git a/learning/demo-app/lib/src/features/rest_crud_demo/presentation/people_screen.dart b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/people_screen.dart new file mode 100644 index 0000000..a5b56a2 --- /dev/null +++ b/learning/demo-app/lib/src/features/rest_crud_demo/presentation/people_screen.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +import '../../../common_widgets/async_value_widget.dart'; +import '../../../common_widgets/two_panel_widget.dart'; +import '../../../routing/app_router.dart'; +import '../../../utils/localization.dart'; +import '../data/people_repository.dart'; +import '../domain/person.dart'; +import 'edit_person_controller.dart'; +import 'edit_person_form.dart'; + +class PeopleScreen extends ConsumerWidget { + const PeopleScreen({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text(context.loc.appTitle), + ), + body: TwoPanelWidget( + firstPanel: RefreshIndicator( + //pull to refresh + onRefresh: () { + ref.invalidate(fetchPeopleProvider); + return ref.read(fetchPeopleProvider.future); + }, + child: AsyncValueWidget>( + value: ref.watch(fetchPeopleProvider), + data: (people) => people.isEmpty + ? Center( + child: Text( + context.loc.noItems, + style: Theme.of(context).textTheme.displaySmall, + textAlign: TextAlign.center, + ), + ) + : ListView.builder( + itemCount: people.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(people[index].name), + onTap: () { + context.goNamed( + SubRoutes.details.name, + pathParameters: { + Parameter.id.name: people[index].id.toString(), + }, + extra: people[index], + ); + }, + trailing: IconButton( + icon: const Icon(Icons.edit), + tooltip: 'Edit '.hardcoded + people[index].name, + onPressed: () { + ref + .read( + editPersonControllerProvider.notifier, + ) + .editPerson(people[index]); + }, + ), + ); + }, + ), + ), + ), + secondPanel: const EditPersonForm(), + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + ref.read(editPersonControllerProvider.notifier).newPerson(); + }, + tooltip: 'click to add a new person'.hardcoded, + child: const Icon(Icons.add), + ), + ); + } +} diff --git a/learning/demo-app/lib/src/routing/app_router.dart b/learning/demo-app/lib/src/routing/app_router.dart new file mode 100644 index 0000000..9027332 --- /dev/null +++ b/learning/demo-app/lib/src/routing/app_router.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import '../features/counter/presentation/counter_screen.dart'; +import '../features/rest_crud_demo/domain/person.dart'; +import '../features/rest_crud_demo/presentation/details_screen.dart'; +import '../features/rest_crud_demo/presentation/people_screen.dart'; +import 'scaffold_with_navigation.dart'; + +part 'app_router.g.dart'; + +// general ideas on navigation see https://m2.material.io/design/navigation/understanding-navigation.html#forward-navigation + +// shell routes, appear in the bottom navigation +// see https://pub.dev/documentation/go_router/latest/go_router/ShellRoute-class.html +enum TopLevelDestinations { people, counter } + +// GlobalKey is a factory, hence each call creates a key +//this is root, even if it navigates to people, it needs a separate key!!! +final _rootNavigatorKey = GlobalKey(); +final _peopleNavigatorKey = + GlobalKey(debugLabel: TopLevelDestinations.people.name); +final _counterNavigatorKey = + GlobalKey(debugLabel: TopLevelDestinations.counter.name); + +// other destinations, reachable from a top level destination +enum SubRoutes { details } + +enum Parameter { id } + +//https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart + +@Riverpod(keepAlive: true) +GoRouter goRouter(GoRouterRef ref) { + return GoRouter( + initialLocation: '/${TopLevelDestinations.people.name}', + navigatorKey: _rootNavigatorKey, + debugLogDiagnostics: true, + routes: [ + // Stateful navigation based on: + // https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart + StatefulShellRoute.indexedStack( + builder: (context, state, navigationShell) { + return ScaffoldWithNavigation(navigationShell: navigationShell); + }, + branches: [ + StatefulShellBranch( + navigatorKey: _peopleNavigatorKey, + routes: [ + // base route people + GoRoute( + path: '/${TopLevelDestinations.people.name}', // path: /people + name: TopLevelDestinations.people.name, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: const PeopleScreen(), + ), + routes: [ + // The details screen to display stacked on navigator of the + // first tab. This will cover screen A but not the application + // shell (bottom navigation bar). + GoRoute( + path: '${SubRoutes.details.name}/:${Parameter.id.name}', + name: SubRoutes.details.name, + builder: (BuildContext context, GoRouterState state) { + // alternatively use https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html + final id = + int.parse(state.pathParameters[Parameter.id.name]!); + final person = _extractPersonFromExtra(state.extra); + return DetailsScreen(id: id, person: person); + }, + ), + ], + ), + ], + ), + StatefulShellBranch( + navigatorKey: _counterNavigatorKey, + routes: [ + GoRoute( + path: '/${TopLevelDestinations.counter.name}', + name: TopLevelDestinations.counter.name, + pageBuilder: (context, state) => NoTransitionPage( + key: state.pageKey, + child: const CounterScreen(), + ), + ), + ], + ), + ], + ), + ], + ); +} + +Person? _extractPersonFromExtra(Object? extra) { + return extra == null + ? null + : extra is Person + ? extra + : extra is Map // if you come back from bottom navigation, e.g. look + // at details of a person, go to counter via bottom navigation, + // use bottom navigation to go to people/home + ? Person.fromJson( + extra as Map, + ) + : null; +} diff --git a/learning/demo-app/lib/src/routing/app_router.g.dart b/learning/demo-app/lib/src/routing/app_router.g.dart new file mode 100644 index 0000000..fbec1e7 --- /dev/null +++ b/learning/demo-app/lib/src/routing/app_router.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_router.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$goRouterHash() => r'caf38f1ae54f10aea6450bd6fc62dae849543827'; + +/// See also [goRouter]. +@ProviderFor(goRouter) +final goRouterProvider = Provider.internal( + goRouter, + name: r'goRouterProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$goRouterHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef GoRouterRef = ProviderRef; +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/learning/demo-app/lib/src/routing/scaffold_with_navigation.dart b/learning/demo-app/lib/src/routing/scaffold_with_navigation.dart new file mode 100644 index 0000000..93bd408 --- /dev/null +++ b/learning/demo-app/lib/src/routing/scaffold_with_navigation.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../constants/breakpoint.dart'; +import '../utils/localization.dart'; + +// see https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart +// and https://github.com/bizz84/tmdb_movie_app_riverpod/blob/main/lib/src/routing/scaffold_with_nested_navigation.dart +class NavigationItem { + NavigationItem({required this.icon, required this.selectedIcon}); + final IconData icon; + final IconData selectedIcon; +} + +final _navigationList = ( + people: NavigationItem(icon: Icons.home_outlined, selectedIcon: Icons.home), + counter: NavigationItem( + icon: Icons.plus_one_outlined, + selectedIcon: Icons.plus_one, + ), +); + +class ScaffoldWithNavigation extends StatelessWidget { + const ScaffoldWithNavigation({ + required this.navigationShell, + Key? key, + }) : super(key: key ?? const ValueKey('ScaffoldWithNavigation')); + final StatefulNavigationShell navigationShell; + + void _goBranch(int index) { + navigationShell.goBranch( + index, + initialLocation: index == navigationShell.currentIndex, + ); + } + + @override + Widget build(BuildContext context) { + final size = MediaQuery.sizeOf(context); + if (size.width < Breakpoint.tablet) { + return ScaffoldWithNavigationBar( + body: navigationShell, + currentIndex: navigationShell.currentIndex, + onDestinationSelected: _goBranch, + ); + } else { + return ScaffoldWithNavigationRail( + body: navigationShell, + currentIndex: navigationShell.currentIndex, + onDestinationSelected: _goBranch, + ); + } + } +} + +class ScaffoldWithNavigationBar extends StatelessWidget { + const ScaffoldWithNavigationBar({ + required this.body, + required this.currentIndex, + required this.onDestinationSelected, + super.key, + }); + final Widget body; + final int currentIndex; + final ValueChanged onDestinationSelected; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: body, + bottomNavigationBar: NavigationBar( + selectedIndex: currentIndex, + destinations: [ + NavigationDestination( + icon: Icon(_navigationList.people.icon), + selectedIcon: Icon(_navigationList.people.selectedIcon), + label: context.loc.home, + ), + NavigationDestination( + icon: Icon(_navigationList.counter.icon), + selectedIcon: Icon(_navigationList.counter.selectedIcon), + label: context.loc.counter, + ), + ], + onDestinationSelected: onDestinationSelected, + ), + ); + } +} + +class ScaffoldWithNavigationRail extends StatelessWidget { + const ScaffoldWithNavigationRail({ + required this.body, + required this.currentIndex, + required this.onDestinationSelected, + super.key, + }); + final Widget body; + final int currentIndex; + final ValueChanged onDestinationSelected; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Row( + children: [ + NavigationRail( + selectedIndex: currentIndex, + onDestinationSelected: onDestinationSelected, + labelType: NavigationRailLabelType.all, + destinations: [ + NavigationRailDestination( + icon: Icon(_navigationList.people.icon), + selectedIcon: Icon(_navigationList.people.selectedIcon), + label: Text(context.loc.home), + ), + NavigationRailDestination( + icon: Icon(_navigationList.counter.icon), + selectedIcon: Icon(_navigationList.counter.selectedIcon), + label: Text(context.loc.counter), + ), + ], + ), + const VerticalDivider(thickness: 1, width: 1), + // This is the main content. + Expanded( + child: body, + ), + ], + ), + ); + } +} diff --git a/learning/demo-app/lib/src/utils/dio_provider.dart b/learning/demo-app/lib/src/utils/dio_provider.dart new file mode 100644 index 0000000..50274ca --- /dev/null +++ b/learning/demo-app/lib/src/utils/dio_provider.dart @@ -0,0 +1,18 @@ +import 'package:dio/dio.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'dio_provider.g.dart'; + +@riverpod +Dio dio(DioRef ref) { + final dio = Dio(); + dio.interceptors.add( + PrettyDioLogger( + requestHeader: true, + responseBody: false, + responseHeader: true, + ), + ); + return dio; +} diff --git a/learning/demo-app/lib/src/utils/dio_provider.g.dart b/learning/demo-app/lib/src/utils/dio_provider.g.dart new file mode 100644 index 0000000..5c1a8bd --- /dev/null +++ b/learning/demo-app/lib/src/utils/dio_provider.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'dio_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +String _$dioHash() => r'50bc684111bbcb930ccaac200da3a7ad761e689b'; + +/// See also [dio]. +@ProviderFor(dio) +final dioProvider = AutoDisposeProvider.internal( + dio, + name: r'dioProvider', + debugGetCreateSourceHash: + const bool.fromEnvironment('dart.vm.product') ? null : _$dioHash, + dependencies: null, + allTransitiveDependencies: null, +); + +typedef DioRef = AutoDisposeProviderRef; +// ignore_for_file: unnecessary_raw_strings, subtype_of_sealed_class, invalid_use_of_internal_member, do_not_use_environment, prefer_const_constructors, public_member_api_docs, avoid_private_typedef_functions diff --git a/learning/demo-app/lib/src/utils/localization.dart b/learning/demo-app/lib/src/utils/localization.dart new file mode 100644 index 0000000..ba3bd15 --- /dev/null +++ b/learning/demo-app/lib/src/utils/localization.dart @@ -0,0 +1,14 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +/// A simple placeholder that can be used to search all the hardcoded strings +/// in the code (useful to identify strings that need to be localized). +/// thanks to https://github.com/bizz84/starter_architecture_flutter_firebase/blob/master/lib/src/localization/string_hardcoded.dart +extension StringHardcoded on String { + String get hardcoded => this; +} + +//thanks to https://codewithandrea.com/articles/flutter-localization-build-context-extension/ +extension LocalizedBuildContext on BuildContext { + AppLocalizations get loc => AppLocalizations.of(this); +} diff --git a/learning/demo-app/lib/src/utils/logger.dart b/learning/demo-app/lib/src/utils/logger.dart new file mode 100644 index 0000000..4ff2a30 --- /dev/null +++ b/learning/demo-app/lib/src/utils/logger.dart @@ -0,0 +1,13 @@ +import 'package:logger/logger.dart'; + +const Level loggerLevel = Level.trace; + +/// Logger for the app. +Logger logger = Logger( + printer: PrettyPrinter( + methodCount: 1, + errorMethodCount: 5, + lineLength: 90, + ), + level: loggerLevel, +); diff --git a/learning/demo-app/linux/.gitignore b/learning/demo-app/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/learning/demo-app/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/learning/demo-app/linux/CMakeLists.txt b/learning/demo-app/linux/CMakeLists.txt new file mode 100644 index 0000000..27054cd --- /dev/null +++ b/learning/demo-app/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "ri_go_demo") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.ri_go_demo") + +# 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) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/learning/demo-app/linux/flutter/CMakeLists.txt b/learning/demo-app/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/linux/flutter/generated_plugin_registrant.cc b/learning/demo-app/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..10e19fe --- /dev/null +++ b/learning/demo-app/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_localization_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterLocalizationPlugin"); + flutter_localization_plugin_register_with_registrar(flutter_localization_registrar); +} diff --git a/learning/demo-app/linux/flutter/generated_plugin_registrant.h b/learning/demo-app/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/linux/flutter/generated_plugins.cmake b/learning/demo-app/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..2284757 --- /dev/null +++ b/learning/demo-app/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_localization +) + +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/learning/demo-app/linux/main.cc b/learning/demo-app/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/learning/demo-app/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/learning/demo-app/linux/my_application.cc b/learning/demo-app/linux/my_application.cc new file mode 100644 index 0000000..fa92caf --- /dev/null +++ b/learning/demo-app/linux/my_application.cc @@ -0,0 +1,123 @@ +#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) + +// 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, "ri_go_demo"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements 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() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/learning/demo-app/linux/my_application.h b/learning/demo-app/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/learning/demo-app/linux/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/learning/demo-app/macos/.gitignore b/learning/demo-app/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/learning/demo-app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/learning/demo-app/macos/Flutter/Flutter-Debug.xcconfig b/learning/demo-app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/learning/demo-app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/learning/demo-app/macos/Flutter/Flutter-Release.xcconfig b/learning/demo-app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/learning/demo-app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/learning/demo-app/macos/Flutter/GeneratedPluginRegistrant.swift b/learning/demo-app/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..e5e17d0 --- /dev/null +++ b/learning/demo-app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import flutter_localization +import shared_preferences_foundation + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterLocalizationPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalizationPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) +} diff --git a/learning/demo-app/macos/Runner.xcodeproj/project.pbxproj b/learning/demo-app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..69d2a8b --- /dev/null +++ b/learning/demo-app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*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 */; }; + EC2E0AF934859E318503F6EF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D6E581200223C1749E1A2040 /* Pods_Runner.framework */; }; + FF27EE9A7B6C94ECE91DEE22 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C43AADF09D01202946577F5F /* Pods_RunnerTests.framework */; }; +/* 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 /* ri_go_demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ri_go_demo.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 = ""; }; + 5F7465BC93D983CE2CE7E803 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 74FA3F5CE5CDE968399F5ED9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 8B355772D46B652034B6C309 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 8E5F4F5978879DB9C225C80F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 905F5E4CC615554A05DEE141 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + BF47B7BAAB87F610E3A3D889 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + C43AADF09D01202946577F5F /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D6E581200223C1749E1A2040 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + FF27EE9A7B6C94ECE91DEE22 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EC2E0AF934859E318503F6EF /* Pods_Runner.framework in Frameworks */, + ); + 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 */, + 8B698BACB8B75A589EEBC88B /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* ri_go_demo.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 = ""; + }; + 8B698BACB8B75A589EEBC88B /* Pods */ = { + isa = PBXGroup; + children = ( + 5F7465BC93D983CE2CE7E803 /* Pods-Runner.debug.xcconfig */, + BF47B7BAAB87F610E3A3D889 /* Pods-Runner.release.xcconfig */, + 74FA3F5CE5CDE968399F5ED9 /* Pods-Runner.profile.xcconfig */, + 8E5F4F5978879DB9C225C80F /* Pods-RunnerTests.debug.xcconfig */, + 8B355772D46B652034B6C309 /* Pods-RunnerTests.release.xcconfig */, + 905F5E4CC615554A05DEE141 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D6E581200223C1749E1A2040 /* Pods_Runner.framework */, + C43AADF09D01202946577F5F /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 05E38B7CEA93FA8606C7C73F /* [CP] Check Pods Manifest.lock */, + 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 = ( + 6C3CFBC548AD20981AF89923 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + DAF6B82A79953741BCDDEF27 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* ri_go_demo.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 */ + 05E38B7CEA93FA8606C7C73F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 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"; + }; + 6C3CFBC548AD20981AF89923 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + DAF6B82A79953741BCDDEF27 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* 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; + baseConfigurationReference = 8E5F4F5978879DB9C225C80F /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.riGoDemo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ri_go_demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ri_go_demo"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 8B355772D46B652034B6C309 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.riGoDemo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ri_go_demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ri_go_demo"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 905F5E4CC615554A05DEE141 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.riGoDemo.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ri_go_demo.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ri_go_demo"; + }; + 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.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + 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.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/learning/demo-app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/learning/demo-app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/learning/demo-app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/learning/demo-app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/learning/demo-app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..506a85a --- /dev/null +++ b/learning/demo-app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/learning/demo-app/macos/Runner.xcworkspace/contents.xcworkspacedata b/learning/demo-app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/learning/demo-app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/learning/demo-app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/learning/demo-app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/learning/demo-app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/learning/demo-app/macos/Runner/AppDelegate.swift b/learning/demo-app/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/learning/demo-app/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/learning/demo-app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/learning/demo-app/macos/Runner/Base.lproj/MainMenu.xib b/learning/demo-app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/learning/demo-app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/learning/demo-app/macos/Runner/Configs/AppInfo.xcconfig b/learning/demo-app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..45cf8e8 --- /dev/null +++ b/learning/demo-app/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 = ri_go_demo + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.riGoDemo + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2024 com.example. All rights reserved. diff --git a/learning/demo-app/macos/Runner/Configs/Debug.xcconfig b/learning/demo-app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/learning/demo-app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/learning/demo-app/macos/Runner/Configs/Release.xcconfig b/learning/demo-app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/learning/demo-app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/learning/demo-app/macos/Runner/Configs/Warnings.xcconfig b/learning/demo-app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/macos/Runner/DebugProfile.entitlements b/learning/demo-app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..08c3ab1 --- /dev/null +++ b/learning/demo-app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/learning/demo-app/macos/Runner/Info.plist b/learning/demo-app/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/macos/Runner/MainFlutterWindow.swift b/learning/demo-app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/macos/Runner/Release.entitlements b/learning/demo-app/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/learning/demo-app/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/learning/demo-app/macos/RunnerTests/RunnerTests.swift b/learning/demo-app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..5418c9f --- /dev/null +++ b/learning/demo-app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import FlutterMacOS +import Cocoa +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/learning/demo-app/pubspec.lock b/learning/demo-app/pubspec.lock new file mode 100644 index 0000000..08dde8b --- /dev/null +++ b/learning/demo-app/pubspec.lock @@ -0,0 +1,975 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + url: "https://pub.dev" + source: hosted + version: "67.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161" + url: "https://pub.dev" + source: hosted + version: "0.11.3" + args: + dependency: transitive + description: + name: args + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + url: "https://pub.dev" + source: hosted + version: "4.0.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + url: "https://pub.dev" + source: hosted + version: "2.4.9" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + build_verify: + dependency: "direct dev" + description: + name: build_verify + sha256: abbb9b9eda076854ac1678d284c053a5ec608e64da741d0801f56d4bbea27e23 + url: "https://pub.dev" + source: hosted + version: "3.1.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + url: "https://pub.dev" + source: hosted + version: "8.9.2" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + ci: + dependency: transitive + description: + name: ci + sha256: "145d095ce05cddac4d797a158bc4cf3b6016d1fe63d8c3d2fbd7212590adca13" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + url: "https://pub.dev" + source: hosted + version: "0.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + url: "https://pub.dev" + source: hosted + version: "4.10.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" + url: "https://pub.dev" + source: hosted + version: "1.7.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + custom_lint: + dependency: transitive + description: + name: custom_lint + sha256: "7c0aec12df22f9082146c354692056677f1e70bc43471644d1fdb36c6fdda799" + url: "https://pub.dev" + source: hosted + version: "0.6.4" + custom_lint_builder: + dependency: transitive + description: + name: custom_lint_builder + sha256: d7dc41e709dde223806660268678be7993559e523eb3164e2a1425fd6f7615a9 + url: "https://pub.dev" + source: hosted + version: "0.6.4" + custom_lint_core: + dependency: transitive + description: + name: custom_lint_core + sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6 + url: "https://pub.dev" + source: hosted + version: "0.6.3" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + url: "https://pub.dev" + source: hosted + version: "2.3.6" + dio: + dependency: "direct main" + description: + name: dio + sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" + url: "https://pub.dev" + source: hosted + version: "5.4.3+1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + flutter_localization: + dependency: "direct main" + description: + name: flutter_localization + sha256: faaeb1eba307473032e2c2af737f36ced61fc98735608410d0a6d9c231b50912 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + flutter_localizations: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + flutter_riverpod: + dependency: "direct main" + description: + name: flutter_riverpod + sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" + url: "https://pub.dev" + source: hosted + version: "2.5.1" + 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: + dependency: "direct main" + description: + name: freezed + sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + url: "https://pub.dev" + source: hosted + version: "2.5.2" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + gap: + dependency: "direct main" + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + url: "https://pub.dev" + source: hosted + version: "3.0.1" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: "771c8feb40ad0ef639973d7ecf1b43d55ffcedb2207fd43fab030f5639e40446" + url: "https://pub.dev" + source: hosted + version: "13.2.4" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + hotreloader: + dependency: transitive + description: + name: hotreloader + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e + url: "https://pub.dev" + source: hosted + version: "4.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 + url: "https://pub.dev" + source: hosted + version: "3.0.0" + logger: + dependency: "direct main" + description: + name: logger + sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pretty_dio_logger: + dependency: "direct main" + description: + name: pretty_dio_logger + sha256: "00b80053063935cf9a6190da344c5373b9d0e92da4c944c878ff2fbef0ef6dc2" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + riverpod: + dependency: transitive + description: + name: riverpod + sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d + url: "https://pub.dev" + source: hosted + version: "2.5.1" + riverpod_analyzer_utils: + dependency: transitive + description: + name: riverpod_analyzer_utils + sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + riverpod_annotation: + dependency: "direct main" + description: + name: riverpod_annotation + sha256: e5e796c0eba4030c704e9dae1b834a6541814963292839dcf9638d53eba84f5c + url: "https://pub.dev" + source: hosted + version: "2.3.5" + riverpod_generator: + dependency: "direct dev" + description: + name: riverpod_generator + sha256: d451608bf17a372025fc36058863737636625dfdb7e3cbf6142e0dfeb366ab22 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + riverpod_lint: + dependency: "direct dev" + description: + name: riverpod_lint + sha256: "3c67c14ccd16f0c9d53e35ef70d06cd9d072e2fb14557326886bbde903b230a5" + url: "https://pub.dev" + source: hosted + version: "2.3.10" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + url: "https://pub.dev" + source: hosted + version: "2.2.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + state_notifier: + dependency: transitive + description: + name: state_notifier + sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb + url: "https://pub.dev" + source: hosted + version: "1.0.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f + url: "https://pub.dev" + source: hosted + version: "1.24.9" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a + url: "https://pub.dev" + source: hosted + version: "0.5.9" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: transitive + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + url: "https://pub.dev" + source: hosted + version: "2.4.5" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a" + url: "https://pub.dev" + source: hosted + version: "5.4.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.3.3 <4.0.0" + flutter: ">=3.19.0" diff --git a/learning/demo-app/pubspec.yaml b/learning/demo-app/pubspec.yaml new file mode 100644 index 0000000..de6af36 --- /dev/null +++ b/learning/demo-app/pubspec.yaml @@ -0,0 +1,44 @@ +name: ri_go_demo +description: A demo project, that uses riverpod and go_router. + +publish_to: "none" + +version: 1.0.0+1 + +environment: + sdk: '>=3.3.3 <4.0.0' + +dependencies: + cupertino_icons: ^1.0.6 + flutter: + sdk: flutter + flutter_localization: ^0.2.0 + intl: ^0.18.1 + # riverpod stuff + flutter_riverpod: ^2.5.1 + riverpod_annotation: ^2.3.5 + + dio: ^5.4.2+1 + pretty_dio_logger: ^1.3.1 + logger: ^2.2.0 + go_router: ^13.2.2 + gap: ^3.0.1 + freezed: ^2.4.7 + freezed_annotation: ^2.4.1 + json_annotation: ^4.8.1 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^3.0.0 + build_runner: ^2.4.6 + build_verify: ^3.1.0 + very_good_analysis: ^5.1.0 + # riverpod generator/dev stuff + riverpod_generator: ^2.4.0 + riverpod_lint: ^2.3.10 + +flutter: + uses-material-design: true + # for localization + generate: true diff --git a/learning/demo-app/test/a11y_test.dart b/learning/demo-app/test/a11y_test.dart new file mode 100644 index 0000000..b88d7b4 --- /dev/null +++ b/learning/demo-app/test/a11y_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:ri_go_demo/main.dart'; +import 'package:ri_go_demo/src/features/rest_crud_demo/data/people_repository.dart'; +import 'package:ri_go_demo/src/utils/logger.dart'; + +import 'fake_repository.dart'; + +// see https://raw.githubusercontent.com/flutter/codelabs/main/namer/step_08/test/a11y_test.dart +void main() { + testWidgets('Follows a11y guidelines for start screen', + (WidgetTester tester) async { + logger.d('test with provider, as they are'); + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget(const ProviderScope(child: MyApp())); + await _testA11y(tester); + handle.dispose(); + }); + + testWidgets('Click edit person and check a11y guidelines', + (WidgetTester tester) async { + logger.d('test with fakeRepository'); + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + ProviderScope( + overrides: [ + peopleRepositoryProvider.overrideWithValue(FakeRepository()), + ], + child: const MyApp(), + ), + ); + // The first frame is a loading state. + expect(find.byType(CircularProgressIndicator), findsOneWidget); + + // Re-render. + await tester.pump(); + + // No longer loading + expect(find.byType(CircularProgressIndicator), findsNothing); + final editIconButtonFinder = find.byIcon(Icons.edit); + // repository holds 2 items, hence expect 2 edit icons + expect(editIconButtonFinder, findsNWidgets(2)); + await tester.press(editIconButtonFinder.first); + await tester.pump(); + + //test form widget for a11y + await _testA11y(tester); + + handle.dispose(); + }); +} + +Future _testA11y(WidgetTester tester) async { + // Checks that tappable nodes have a minimum size of 48 by 48 pixels + // for Android. + await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); + + // Checks that tappable nodes have a minimum size of 44 by 44 pixels + // for iOS. + await expectLater(tester, meetsGuideline(iOSTapTargetGuideline)); + + // Checks that touch targets with a tap or long press action are labeled. + await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); + + // Checks whether semantic nodes meet the minimum text contrast levels. + // The recommended text contrast is 3:1 for larger text + // (18 point and above regular). + await expectLater(tester, meetsGuideline(textContrastGuideline)); + + // @see https://github.com/rrousselGit/riverpod/issues/1941 + await tester.pumpWidget(Container()); + await tester.pumpAndSettle(); +} diff --git a/learning/demo-app/test/fake_repository.dart b/learning/demo-app/test/fake_repository.dart new file mode 100644 index 0000000..edea403 --- /dev/null +++ b/learning/demo-app/test/fake_repository.dart @@ -0,0 +1,261 @@ +import 'package:dio/dio.dart'; +import 'package:ri_go_demo/src/features/rest_crud_demo/data/people_repository.dart'; +import 'package:ri_go_demo/src/features/rest_crud_demo/domain/person.dart'; +import 'package:ri_go_demo/src/utils/logger.dart'; + +/// A mocked implementation of Repository +/// @see https://riverpod.dev/docs/cookbooks/testing +class FakeRepository implements PeopleRepository { + @override + Future deletePerson(int id) { + return Future.value(true); + } + + @override + Dio get dio => throw UnimplementedError(); //FakeDio(); + + @override + Future> getPeople() { + logger.d('fake_repository.fetchPeople'); + final people = [ + const Person(id: 1, name: 'Eva', imageUrl: ''), + const Person(id: 2, name: 'Lotta', imageUrl: ''), + ]; + return Future.value(people); + } + + @override + Future getPersonById({required int id}) { + throw UnimplementedError(); + } + + @override + Future savePerson({required Person person}) { + throw UnimplementedError(); + } + + @override + Future updatePerson({required Person person}) { + throw UnimplementedError(); + } +} + +class FakeDio implements Dio { + @override + HttpClientAdapter httpClientAdapter = HttpClientAdapter(); + + @override + BaseOptions options = BaseOptions(); + + @override + Transformer transformer = BackgroundTransformer(); + + @override + void close({bool force = false}) {} + + @override + Future> delete( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + }) { + throw UnimplementedError(); + } + + @override + Future> deleteUri( + Uri uri, { + Object? data, + Options? options, + CancelToken? cancelToken, + }) { + throw UnimplementedError(); + } + + @override + // ignore: strict_raw_type, type_annotate_public_apis + Future download( + String urlPath, + // ignore: type_annotate_public_apis + savePath, { + ProgressCallback? onReceiveProgress, + Map? queryParameters, + CancelToken? cancelToken, + bool deleteOnError = true, + String lengthHeader = Headers.contentLengthHeader, + Object? data, + Options? options, + }) { + throw UnimplementedError(); + } + + @override + // ignore: strict_raw_type, type_annotate_public_apis + Future downloadUri( + Uri uri, + // ignore: type_annotate_public_apis + savePath, { + ProgressCallback? onReceiveProgress, + CancelToken? cancelToken, + bool deleteOnError = true, + String lengthHeader = Headers.contentLengthHeader, + Object? data, + Options? options, + }) { + throw UnimplementedError(); + } + + @override + Future> fetch(RequestOptions requestOptions) { + throw UnimplementedError(); + } + + @override + Future> get( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> getUri( + Uri uri, { + Object? data, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> head( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + }) { + throw UnimplementedError(); + } + + @override + Future> headUri( + Uri uri, { + Object? data, + Options? options, + CancelToken? cancelToken, + }) { + throw UnimplementedError(); + } + + @override + Interceptors get interceptors => throw UnimplementedError(); + + @override + Future> patch( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> patchUri( + Uri uri, { + Object? data, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> post( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> postUri( + Uri uri, { + Object? data, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> put( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> putUri( + Uri uri, { + Object? data, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> request( + String url, { + Object? data, + Map? queryParameters, + CancelToken? cancelToken, + Options? options, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } + + @override + Future> requestUri( + Uri uri, { + Object? data, + CancelToken? cancelToken, + Options? options, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + throw UnimplementedError(); + } +} diff --git a/learning/demo-app/untranslated_messages.json b/learning/demo-app/untranslated_messages.json new file mode 100644 index 0000000..6114a8d --- /dev/null +++ b/learning/demo-app/untranslated_messages.json @@ -0,0 +1,11 @@ +{ + "de": [ + "deselect", + "requiredField", + "labelName", + "labelImageUrl", + "delete", + "details", + "noItems" + ] +} diff --git a/learning/demo-app/web/favicon.png b/learning/demo-app/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/learning/demo-app/web/favicon.png differ diff --git a/learning/demo-app/web/icons/Icon-192.png b/learning/demo-app/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/learning/demo-app/web/icons/Icon-192.png differ diff --git a/learning/demo-app/web/icons/Icon-512.png b/learning/demo-app/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/learning/demo-app/web/icons/Icon-512.png differ diff --git a/learning/demo-app/web/icons/Icon-maskable-192.png b/learning/demo-app/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/learning/demo-app/web/icons/Icon-maskable-192.png differ diff --git a/learning/demo-app/web/icons/Icon-maskable-512.png b/learning/demo-app/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/learning/demo-app/web/icons/Icon-maskable-512.png differ diff --git a/learning/demo-app/web/index.html b/learning/demo-app/web/index.html new file mode 100644 index 0000000..3374732 --- /dev/null +++ b/learning/demo-app/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + ri_go_demo + + + + + + + + + + diff --git a/learning/demo-app/web/manifest.json b/learning/demo-app/web/manifest.json new file mode 100644 index 0000000..42dd374 --- /dev/null +++ b/learning/demo-app/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "ri_go_demo", + "short_name": "ri_go_demo", + "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/learning/demo-app/windows/.gitignore b/learning/demo-app/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/CMakeLists.txt b/learning/demo-app/windows/CMakeLists.txt new file mode 100644 index 0000000..090b111 --- /dev/null +++ b/learning/demo-app/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(ri_go_demo 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 "ri_go_demo") + +# 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/learning/demo-app/windows/flutter/CMakeLists.txt b/learning/demo-app/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/flutter/generated_plugin_registrant.cc b/learning/demo-app/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..9f5b3e5 --- /dev/null +++ b/learning/demo-app/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterLocalizationPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterLocalizationPluginCApi")); +} diff --git a/learning/demo-app/windows/flutter/generated_plugin_registrant.h b/learning/demo-app/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/flutter/generated_plugins.cmake b/learning/demo-app/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f040a63 --- /dev/null +++ b/learning/demo-app/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flutter_localization +) + +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/learning/demo-app/windows/runner/CMakeLists.txt b/learning/demo-app/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/runner/Runner.rc b/learning/demo-app/windows/runner/Runner.rc new file mode 100644 index 0000000..a24a9cc --- /dev/null +++ b/learning/demo-app/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", "ri_go_demo" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "ri_go_demo" "\0" + VALUE "LegalCopyright", "Copyright (C) 2024 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "ri_go_demo.exe" "\0" + VALUE "ProductName", "ri_go_demo" "\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/learning/demo-app/windows/runner/flutter_window.cpp b/learning/demo-app/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/runner/flutter_window.h b/learning/demo-app/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/runner/main.cpp b/learning/demo-app/windows/runner/main.cpp new file mode 100644 index 0000000..5e56ff7 --- /dev/null +++ b/learning/demo-app/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"ri_go_demo", 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/learning/demo-app/windows/runner/resource.h b/learning/demo-app/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/runner/resources/app_icon.ico b/learning/demo-app/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/learning/demo-app/windows/runner/resources/app_icon.ico differ diff --git a/learning/demo-app/windows/runner/runner.exe.manifest b/learning/demo-app/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/learning/demo-app/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/learning/demo-app/windows/runner/utils.cpp b/learning/demo-app/windows/runner/utils.cpp new file mode 100644 index 0000000..b2b0873 --- /dev/null +++ b/learning/demo-app/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length <= 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/learning/demo-app/windows/runner/utils.h b/learning/demo-app/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/runner/win32_window.cpp b/learning/demo-app/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/learning/demo-app/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/learning/demo-app/windows/runner/win32_window.h b/learning/demo-app/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/learning/demo-app/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_ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..88c8fc3 --- /dev/null +++ b/public/index.html @@ -0,0 +1,17 @@ + + + + + learning flutter + + +
+

Learning Flutter

+
+
+ +
+ \ No newline at end of file