├── .gitignore ├── .metadata ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── stonks │ │ │ │ └── mood │ │ │ │ └── mood │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── images │ ├── drive.svg │ ├── icon.png │ └── yoga.svg ├── examples └── screenshots │ └── main.png ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-50x50@1x.png │ │ ├── Icon-App-50x50@2x.png │ │ ├── Icon-App-57x57@1x.png │ │ ├── Icon-App-57x57@2x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-72x72@1x.png │ │ ├── Icon-App-72x72@2x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── constants.dart ├── database │ └── database.dart ├── main.dart ├── models │ ├── activity.dart │ ├── challenge.dart │ └── reward.dart ├── providers │ ├── activities.dart │ ├── challenges.dart │ └── rewards.dart ├── screens │ ├── challenge_screen.dart │ ├── check_back_screen.dart │ ├── home_screen.dart │ ├── review_screen.dart │ ├── reward_screen.dart │ ├── schedule_screen.dart │ └── triage_screen.dart └── widgets │ ├── action_panel.dart │ ├── custom_safe_area.dart │ ├── emoji_button.dart │ ├── knob_slider.dart │ ├── loading_screen.dart │ └── slider_question.dart ├── pubspec.lock └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7c9f95ac0aca4f43efae9b4377137f3f47f2fb06 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Helm 2 | Helm is an app that gamifies stress/anxiety/depression management in an actionable manner to provide relief. 3 | 4 | To try it out, [setup flutter on your machine](https://flutter.dev/docs/get-started/install) and clone the project source. 5 | ```bash 6 | # Clone project 7 | git clone https://github.com/chuabingquan/helm.git 8 | 9 | # For iOS deployment 10 | cd helm/ 11 | rm -rf ios android/ 12 | flutter create . 13 | flutter pub run flutter_launcher_icons:main 14 | flutter run 15 | 16 | # For Android Deployment 17 | cd helm/ 18 | flutter run 19 | ``` 20 | 21 | ## Context 22 | As part of [NUS DSC's Hack For Good 2020](https://dsc.comp.nus.edu.sg/hackforgood2020), we developed Helm to address the problem statement of using technology to aid issues centered around stress/anxiety/depression. More info can be found in our [pitch deck](https://docs.google.com/presentation/d/1hH11FbyhlhNWxPFe0hKWraFyy2q5O0zYZZu8inYZYG8/edit?usp=sharing). 23 | 24 | ## How Does it Work? 25 | - Helm quantifies the difference in a user's actual and ideal stress/anxiety/depression level as credits to be spent on a prescribed set of activities before the day ends. By doing so, users inadvertently take actionable steps to deal with their condition. 26 | 27 | - Users then decide on the activities to spend their credits on, the reward they get for completing them (to positively reinforce the habit of taking actionable steps to deal with their condition), and a time for Helm to notify them to check back in. 28 | 29 | - When users check back in, Helm prompts them to reward themselves, then proceeds to re-evaluate their stress/anxiety/depression levels. 30 | 31 | - Should their stress/anxiety/depression levels be not up to mark, users get to re-attempt the challenge with their re-evaluated levels, restarting the whole process. 32 | 33 | ## Screenshots 34 | ![Helm app screenshots](examples/screenshots/main.png) 35 | 36 | ## Credits 37 | - [Mary Heng](https://github.com/maryheng) 38 | - [Bing Quan](https://github.com/chuabingquan) -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.stonks.helm" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 10 | 11 | 15 | 22 | 26 | 30 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/stonks/mood/mood/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.stonks.helm 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /assets/images/drive.svg: -------------------------------------------------------------------------------- 1 | runner_start -------------------------------------------------------------------------------- /assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/assets/images/icon.png -------------------------------------------------------------------------------- /assets/images/yoga.svg: -------------------------------------------------------------------------------- 1 | mindfulness -------------------------------------------------------------------------------- /examples/screenshots/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/examples/screenshots/main.png -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | post_install do |installer| 82 | installer.pods_project.targets.each do |target| 83 | target.build_configurations.each do |config| 84 | config.build_settings['ENABLE_BITCODE'] = 'NO' 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_local_notifications (0.0.1): 4 | - Flutter 5 | - FMDB (2.7.5): 6 | - FMDB/standard (= 2.7.5) 7 | - FMDB/standard (2.7.5) 8 | - path_provider (0.0.1): 9 | - Flutter 10 | - path_provider_macos (0.0.1): 11 | - Flutter 12 | - sqflite (0.0.1): 13 | - Flutter 14 | - FMDB (~> 2.7.2) 15 | 16 | DEPENDENCIES: 17 | - Flutter (from `Flutter`) 18 | - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) 19 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 20 | - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) 21 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 22 | 23 | SPEC REPOS: 24 | trunk: 25 | - FMDB 26 | 27 | EXTERNAL SOURCES: 28 | Flutter: 29 | :path: Flutter 30 | flutter_local_notifications: 31 | :path: ".symlinks/plugins/flutter_local_notifications/ios" 32 | path_provider: 33 | :path: ".symlinks/plugins/path_provider/ios" 34 | path_provider_macos: 35 | :path: ".symlinks/plugins/path_provider_macos/ios" 36 | sqflite: 37 | :path: ".symlinks/plugins/sqflite/ios" 38 | 39 | SPEC CHECKSUMS: 40 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 41 | flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186 42 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 43 | path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d 44 | path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 45 | sqflite: 4001a31ff81d210346b500c55b17f4d6c7589dd0 46 | 47 | PODFILE CHECKSUM: c34e2287a9ccaa606aeceab922830efb9a6ff69a 48 | 49 | COCOAPODS: 1.8.3 50 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 1E0064D01192A5D855C7CC7C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4F79109C17624C191B3A315 /* Pods_Runner.framework */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 1F682888619725305B9656E0 /* 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 = ""; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 38 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 39 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 40 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 41 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 42 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 44 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | B4F79109C17624C191B3A315 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | CC144868051890330377EB2F /* 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 = ""; }; 49 | F936A1EE97F52DDCDB5EE553 /* 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 = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 1E0064D01192A5D855C7CC7C /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 5075D4304C2E22A4F564641F /* Pods */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | 1F682888619725305B9656E0 /* Pods-Runner.debug.xcconfig */, 68 | F936A1EE97F52DDCDB5EE553 /* Pods-Runner.release.xcconfig */, 69 | CC144868051890330377EB2F /* Pods-Runner.profile.xcconfig */, 70 | ); 71 | path = Pods; 72 | sourceTree = ""; 73 | }; 74 | 6FB6BB698445B5D512DA866D /* Frameworks */ = { 75 | isa = PBXGroup; 76 | children = ( 77 | B4F79109C17624C191B3A315 /* Pods_Runner.framework */, 78 | ); 79 | name = Frameworks; 80 | sourceTree = ""; 81 | }; 82 | 9740EEB11CF90186004384FC /* Flutter */ = { 83 | isa = PBXGroup; 84 | children = ( 85 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 86 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 87 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 88 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 89 | ); 90 | name = Flutter; 91 | sourceTree = ""; 92 | }; 93 | 97C146E51CF9000F007C117D = { 94 | isa = PBXGroup; 95 | children = ( 96 | 9740EEB11CF90186004384FC /* Flutter */, 97 | 97C146F01CF9000F007C117D /* Runner */, 98 | 97C146EF1CF9000F007C117D /* Products */, 99 | 5075D4304C2E22A4F564641F /* Pods */, 100 | 6FB6BB698445B5D512DA866D /* Frameworks */, 101 | ); 102 | sourceTree = ""; 103 | }; 104 | 97C146EF1CF9000F007C117D /* Products */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 97C146EE1CF9000F007C117D /* Runner.app */, 108 | ); 109 | name = Products; 110 | sourceTree = ""; 111 | }; 112 | 97C146F01CF9000F007C117D /* Runner */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 116 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 117 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 118 | 97C147021CF9000F007C117D /* Info.plist */, 119 | 97C146F11CF9000F007C117D /* Supporting Files */, 120 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 121 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 122 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 123 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 124 | ); 125 | path = Runner; 126 | sourceTree = ""; 127 | }; 128 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | ); 132 | name = "Supporting Files"; 133 | sourceTree = ""; 134 | }; 135 | /* End PBXGroup section */ 136 | 137 | /* Begin PBXNativeTarget section */ 138 | 97C146ED1CF9000F007C117D /* Runner */ = { 139 | isa = PBXNativeTarget; 140 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 141 | buildPhases = ( 142 | 81876A32DC5F3D6A8967E401 /* [CP] Check Pods Manifest.lock */, 143 | 9740EEB61CF901F6004384FC /* Run Script */, 144 | 97C146EA1CF9000F007C117D /* Sources */, 145 | 97C146EB1CF9000F007C117D /* Frameworks */, 146 | 97C146EC1CF9000F007C117D /* Resources */, 147 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 148 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 149 | 360774CD843DF6A9C5D262C1 /* [CP] Embed Pods Frameworks */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = Runner; 156 | productName = Runner; 157 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 158 | productType = "com.apple.product-type.application"; 159 | }; 160 | /* End PBXNativeTarget section */ 161 | 162 | /* Begin PBXProject section */ 163 | 97C146E61CF9000F007C117D /* Project object */ = { 164 | isa = PBXProject; 165 | attributes = { 166 | LastUpgradeCheck = 1020; 167 | ORGANIZATIONNAME = ""; 168 | TargetAttributes = { 169 | 97C146ED1CF9000F007C117D = { 170 | CreatedOnToolsVersion = 7.3.1; 171 | LastSwiftMigration = 1100; 172 | }; 173 | }; 174 | }; 175 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 176 | compatibilityVersion = "Xcode 9.3"; 177 | developmentRegion = en; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | Base, 182 | ); 183 | mainGroup = 97C146E51CF9000F007C117D; 184 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 185 | projectDirPath = ""; 186 | projectRoot = ""; 187 | targets = ( 188 | 97C146ED1CF9000F007C117D /* Runner */, 189 | ); 190 | }; 191 | /* End PBXProject section */ 192 | 193 | /* Begin PBXResourcesBuildPhase section */ 194 | 97C146EC1CF9000F007C117D /* Resources */ = { 195 | isa = PBXResourcesBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 199 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 200 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 201 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXResourcesBuildPhase section */ 206 | 207 | /* Begin PBXShellScriptBuildPhase section */ 208 | 360774CD843DF6A9C5D262C1 /* [CP] Embed Pods Frameworks */ = { 209 | isa = PBXShellScriptBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | inputFileListPaths = ( 214 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 215 | ); 216 | name = "[CP] Embed Pods Frameworks"; 217 | outputFileListPaths = ( 218 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 219 | ); 220 | runOnlyForDeploymentPostprocessing = 0; 221 | shellPath = /bin/sh; 222 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 223 | showEnvVarsInLog = 0; 224 | }; 225 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 226 | isa = PBXShellScriptBuildPhase; 227 | buildActionMask = 2147483647; 228 | files = ( 229 | ); 230 | inputPaths = ( 231 | ); 232 | name = "Thin Binary"; 233 | outputPaths = ( 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | shellPath = /bin/sh; 237 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 238 | }; 239 | 81876A32DC5F3D6A8967E401 /* [CP] Check Pods Manifest.lock */ = { 240 | isa = PBXShellScriptBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | ); 244 | inputFileListPaths = ( 245 | ); 246 | inputPaths = ( 247 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 248 | "${PODS_ROOT}/Manifest.lock", 249 | ); 250 | name = "[CP] Check Pods Manifest.lock"; 251 | outputFileListPaths = ( 252 | ); 253 | outputPaths = ( 254 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | shellPath = /bin/sh; 258 | 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"; 259 | showEnvVarsInLog = 0; 260 | }; 261 | 9740EEB61CF901F6004384FC /* Run Script */ = { 262 | isa = PBXShellScriptBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | inputPaths = ( 267 | ); 268 | name = "Run Script"; 269 | outputPaths = ( 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | shellPath = /bin/sh; 273 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 274 | }; 275 | /* End PBXShellScriptBuildPhase section */ 276 | 277 | /* Begin PBXSourcesBuildPhase section */ 278 | 97C146EA1CF9000F007C117D /* Sources */ = { 279 | isa = PBXSourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 283 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 284 | ); 285 | runOnlyForDeploymentPostprocessing = 0; 286 | }; 287 | /* End PBXSourcesBuildPhase section */ 288 | 289 | /* Begin PBXVariantGroup section */ 290 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 291 | isa = PBXVariantGroup; 292 | children = ( 293 | 97C146FB1CF9000F007C117D /* Base */, 294 | ); 295 | name = Main.storyboard; 296 | sourceTree = ""; 297 | }; 298 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 299 | isa = PBXVariantGroup; 300 | children = ( 301 | 97C147001CF9000F007C117D /* Base */, 302 | ); 303 | name = LaunchScreen.storyboard; 304 | sourceTree = ""; 305 | }; 306 | /* End PBXVariantGroup section */ 307 | 308 | /* Begin XCBuildConfiguration section */ 309 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 310 | isa = XCBuildConfiguration; 311 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_ANALYZER_NONNULL = YES; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_COMMA = YES; 322 | CLANG_WARN_CONSTANT_CONVERSION = YES; 323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_EMPTY_BODY = YES; 326 | CLANG_WARN_ENUM_CONVERSION = YES; 327 | CLANG_WARN_INFINITE_RECURSION = YES; 328 | CLANG_WARN_INT_CONVERSION = YES; 329 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 330 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 331 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 334 | CLANG_WARN_STRICT_PROTOTYPES = YES; 335 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 336 | CLANG_WARN_UNREACHABLE_CODE = YES; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 339 | COPY_PHASE_STRIP = NO; 340 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 341 | ENABLE_NS_ASSERTIONS = NO; 342 | ENABLE_STRICT_OBJC_MSGSEND = YES; 343 | GCC_C_LANGUAGE_STANDARD = gnu99; 344 | GCC_NO_COMMON_BLOCKS = YES; 345 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 346 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 347 | GCC_WARN_UNDECLARED_SELECTOR = YES; 348 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 349 | GCC_WARN_UNUSED_FUNCTION = YES; 350 | GCC_WARN_UNUSED_VARIABLE = YES; 351 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 352 | MTL_ENABLE_DEBUG_INFO = NO; 353 | SDKROOT = iphoneos; 354 | SUPPORTED_PLATFORMS = iphoneos; 355 | TARGETED_DEVICE_FAMILY = "1,2"; 356 | VALIDATE_PRODUCT = YES; 357 | }; 358 | name = Profile; 359 | }; 360 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 361 | isa = XCBuildConfiguration; 362 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 363 | buildSettings = { 364 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 365 | CLANG_ENABLE_MODULES = YES; 366 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 367 | DEVELOPMENT_TEAM = 589NVB575D; 368 | ENABLE_BITCODE = NO; 369 | FRAMEWORK_SEARCH_PATHS = ( 370 | "$(inherited)", 371 | "$(PROJECT_DIR)/Flutter", 372 | ); 373 | INFOPLIST_FILE = Runner/Info.plist; 374 | LD_RUNPATH_SEARCH_PATHS = ( 375 | "$(inherited)", 376 | "@executable_path/Frameworks", 377 | ); 378 | LIBRARY_SEARCH_PATHS = ( 379 | "$(inherited)", 380 | "$(PROJECT_DIR)/Flutter", 381 | ); 382 | PRODUCT_BUNDLE_IDENTIFIER = com.stonks.helm; 383 | PRODUCT_NAME = "$(TARGET_NAME)"; 384 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 385 | SWIFT_VERSION = 5.0; 386 | VERSIONING_SYSTEM = "apple-generic"; 387 | }; 388 | name = Profile; 389 | }; 390 | 97C147031CF9000F007C117D /* Debug */ = { 391 | isa = XCBuildConfiguration; 392 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | CLANG_ANALYZER_NONNULL = YES; 396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 397 | CLANG_CXX_LIBRARY = "libc++"; 398 | CLANG_ENABLE_MODULES = YES; 399 | CLANG_ENABLE_OBJC_ARC = YES; 400 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 401 | CLANG_WARN_BOOL_CONVERSION = YES; 402 | CLANG_WARN_COMMA = YES; 403 | CLANG_WARN_CONSTANT_CONVERSION = YES; 404 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 405 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 406 | CLANG_WARN_EMPTY_BODY = YES; 407 | CLANG_WARN_ENUM_CONVERSION = YES; 408 | CLANG_WARN_INFINITE_RECURSION = YES; 409 | CLANG_WARN_INT_CONVERSION = YES; 410 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 411 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 412 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 415 | CLANG_WARN_STRICT_PROTOTYPES = YES; 416 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 417 | CLANG_WARN_UNREACHABLE_CODE = YES; 418 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 419 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 420 | COPY_PHASE_STRIP = NO; 421 | DEBUG_INFORMATION_FORMAT = dwarf; 422 | ENABLE_STRICT_OBJC_MSGSEND = YES; 423 | ENABLE_TESTABILITY = YES; 424 | GCC_C_LANGUAGE_STANDARD = gnu99; 425 | GCC_DYNAMIC_NO_PIC = NO; 426 | GCC_NO_COMMON_BLOCKS = YES; 427 | GCC_OPTIMIZATION_LEVEL = 0; 428 | GCC_PREPROCESSOR_DEFINITIONS = ( 429 | "DEBUG=1", 430 | "$(inherited)", 431 | ); 432 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 433 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 434 | GCC_WARN_UNDECLARED_SELECTOR = YES; 435 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 436 | GCC_WARN_UNUSED_FUNCTION = YES; 437 | GCC_WARN_UNUSED_VARIABLE = YES; 438 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 439 | MTL_ENABLE_DEBUG_INFO = YES; 440 | ONLY_ACTIVE_ARCH = YES; 441 | SDKROOT = iphoneos; 442 | TARGETED_DEVICE_FAMILY = "1,2"; 443 | }; 444 | name = Debug; 445 | }; 446 | 97C147041CF9000F007C117D /* Release */ = { 447 | isa = XCBuildConfiguration; 448 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 449 | buildSettings = { 450 | ALWAYS_SEARCH_USER_PATHS = NO; 451 | CLANG_ANALYZER_NONNULL = YES; 452 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 453 | CLANG_CXX_LIBRARY = "libc++"; 454 | CLANG_ENABLE_MODULES = YES; 455 | CLANG_ENABLE_OBJC_ARC = YES; 456 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 457 | CLANG_WARN_BOOL_CONVERSION = YES; 458 | CLANG_WARN_COMMA = YES; 459 | CLANG_WARN_CONSTANT_CONVERSION = YES; 460 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 461 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 462 | CLANG_WARN_EMPTY_BODY = YES; 463 | CLANG_WARN_ENUM_CONVERSION = YES; 464 | CLANG_WARN_INFINITE_RECURSION = YES; 465 | CLANG_WARN_INT_CONVERSION = YES; 466 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 467 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 468 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 469 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 470 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 471 | CLANG_WARN_STRICT_PROTOTYPES = YES; 472 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 473 | CLANG_WARN_UNREACHABLE_CODE = YES; 474 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 475 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 476 | COPY_PHASE_STRIP = NO; 477 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 478 | ENABLE_NS_ASSERTIONS = NO; 479 | ENABLE_STRICT_OBJC_MSGSEND = YES; 480 | GCC_C_LANGUAGE_STANDARD = gnu99; 481 | GCC_NO_COMMON_BLOCKS = YES; 482 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 483 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 484 | GCC_WARN_UNDECLARED_SELECTOR = YES; 485 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 486 | GCC_WARN_UNUSED_FUNCTION = YES; 487 | GCC_WARN_UNUSED_VARIABLE = YES; 488 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 489 | MTL_ENABLE_DEBUG_INFO = NO; 490 | SDKROOT = iphoneos; 491 | SUPPORTED_PLATFORMS = iphoneos; 492 | SWIFT_COMPILATION_MODE = wholemodule; 493 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 494 | TARGETED_DEVICE_FAMILY = "1,2"; 495 | VALIDATE_PRODUCT = YES; 496 | }; 497 | name = Release; 498 | }; 499 | 97C147061CF9000F007C117D /* Debug */ = { 500 | isa = XCBuildConfiguration; 501 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 502 | buildSettings = { 503 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 504 | CLANG_ENABLE_MODULES = YES; 505 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 506 | DEVELOPMENT_TEAM = 589NVB575D; 507 | ENABLE_BITCODE = NO; 508 | FRAMEWORK_SEARCH_PATHS = ( 509 | "$(inherited)", 510 | "$(PROJECT_DIR)/Flutter", 511 | ); 512 | INFOPLIST_FILE = Runner/Info.plist; 513 | LD_RUNPATH_SEARCH_PATHS = ( 514 | "$(inherited)", 515 | "@executable_path/Frameworks", 516 | ); 517 | LIBRARY_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "$(PROJECT_DIR)/Flutter", 520 | ); 521 | PRODUCT_BUNDLE_IDENTIFIER = com.stonks.helm; 522 | PRODUCT_NAME = "$(TARGET_NAME)"; 523 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 524 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 525 | SWIFT_VERSION = 5.0; 526 | VERSIONING_SYSTEM = "apple-generic"; 527 | }; 528 | name = Debug; 529 | }; 530 | 97C147071CF9000F007C117D /* Release */ = { 531 | isa = XCBuildConfiguration; 532 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 533 | buildSettings = { 534 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 535 | CLANG_ENABLE_MODULES = YES; 536 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 537 | DEVELOPMENT_TEAM = 589NVB575D; 538 | ENABLE_BITCODE = NO; 539 | FRAMEWORK_SEARCH_PATHS = ( 540 | "$(inherited)", 541 | "$(PROJECT_DIR)/Flutter", 542 | ); 543 | INFOPLIST_FILE = Runner/Info.plist; 544 | LD_RUNPATH_SEARCH_PATHS = ( 545 | "$(inherited)", 546 | "@executable_path/Frameworks", 547 | ); 548 | LIBRARY_SEARCH_PATHS = ( 549 | "$(inherited)", 550 | "$(PROJECT_DIR)/Flutter", 551 | ); 552 | PRODUCT_BUNDLE_IDENTIFIER = com.stonks.helm; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 555 | SWIFT_VERSION = 5.0; 556 | VERSIONING_SYSTEM = "apple-generic"; 557 | }; 558 | name = Release; 559 | }; 560 | /* End XCBuildConfiguration section */ 561 | 562 | /* Begin XCConfigurationList section */ 563 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 564 | isa = XCConfigurationList; 565 | buildConfigurations = ( 566 | 97C147031CF9000F007C117D /* Debug */, 567 | 97C147041CF9000F007C117D /* Release */, 568 | 249021D3217E4FDB00AE95B9 /* Profile */, 569 | ); 570 | defaultConfigurationIsVisible = 0; 571 | defaultConfigurationName = Release; 572 | }; 573 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 574 | isa = XCConfigurationList; 575 | buildConfigurations = ( 576 | 97C147061CF9000F007C117D /* Debug */, 577 | 97C147071CF9000F007C117D /* Release */, 578 | 249021D4217E4FDB00AE95B9 /* Profile */, 579 | ); 580 | defaultConfigurationIsVisible = 0; 581 | defaultConfigurationName = Release; 582 | }; 583 | /* End XCConfigurationList section */ 584 | }; 585 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 586 | } 587 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | if #available(iOS 10.0, *) { 11 | UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate 12 | } 13 | GeneratedPluginRegistrant.register(with: self) 14 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chuabingquan/helm/6c00ea1408de15f59f5bd81233ecc132ece174ba/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | 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. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | Helm 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/constants.dart: -------------------------------------------------------------------------------- 1 | library constants; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | const PRIMARY_COLOR = MaterialColor(0xffffd98e, {}); 6 | const ACCENT_COLOR = MaterialColor(0xff314675, {}); 7 | 8 | const DATABASE_NAME = 'helm'; -------------------------------------------------------------------------------- /lib/database/database.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:path/path.dart'; 4 | import 'package:sqflite/sqflite.dart'; 5 | import '../models/activity.dart'; 6 | import '../models/reward.dart'; 7 | 8 | final _defaultRewards = [ 9 | Reward( 10 | id: 1, 11 | name: 'Ice Cream', 12 | emoji: '🍦', 13 | ), 14 | Reward( 15 | id: 2, 16 | name: 'Volunteer', 17 | emoji: '🙋', 18 | ), 19 | Reward( 20 | id: 3, 21 | name: 'Personal Praise', 22 | emoji: '🙌', 23 | ), 24 | Reward( 25 | id: 4, 26 | name: 'Mobile Game', 27 | emoji: '🎮', 28 | ), 29 | Reward( 30 | id: 5, 31 | name: 'Buy something nice', 32 | emoji: '🛒', 33 | ), 34 | Reward( 35 | id: 6, 36 | name: 'Nap', 37 | emoji: '😴', 38 | ), 39 | ]; 40 | 41 | final _defaultActivities = [ 42 | Activity( 43 | id: 1, 44 | name: 'Meditate', 45 | description: 'Do 10 sets of breathing exercises', 46 | emoji: '🧘', 47 | credits: 1, 48 | ), 49 | Activity( 50 | id: 2, 51 | name: 'Watch a TED Talk', 52 | description: 'Keep yourself motivated!', 53 | emoji: '📺', 54 | credits: 1, 55 | ), 56 | Activity( 57 | id: 3, 58 | name: 'Jog', 59 | description: 'Go for a 1/2 hour jog', 60 | emoji: '🏃', 61 | credits: 2, 62 | ), 63 | Activity( 64 | id: 4, 65 | name: 'Read', 66 | description: 'Keep your mind focused on some other literature', 67 | emoji: '📚', 68 | credits: 2, 69 | ), 70 | Activity( 71 | id: 5, 72 | name: 'Stroll', 73 | description: 'Take a short break and enjoy the scenery', 74 | emoji: '🚶🏻‍♀️', 75 | credits: 2, 76 | ), 77 | Activity( 78 | id: 6, 79 | name: 'Write', 80 | description: 'Vent your anxiety through writing', 81 | emoji: '📝', 82 | credits: 3, 83 | ), 84 | Activity( 85 | id: 7, 86 | name: 'Bake', 87 | description: 'Bake a cake', 88 | emoji: '🎂', 89 | credits: 3, 90 | ), 91 | Activity( 92 | id: 8, 93 | name: 'Talk to a friend', 94 | description: 'A listening ear helps!', 95 | emoji: '🗣', 96 | credits: 4, 97 | ), 98 | Activity( 99 | id: 9, 100 | name: 'Do someone a favour', 101 | description: 'Helping others helps you to feel better', 102 | emoji: '💪', 103 | credits: 4, 104 | ), 105 | ]; 106 | 107 | Future connectToDB(String dbName) async { 108 | try { 109 | return await openDatabase( 110 | join(await getDatabasesPath(), '$dbName.db'), 111 | onCreate: (db, version) async { 112 | try { 113 | final batch = db.batch(); 114 | 115 | // Create and insert default rewards into database. 116 | batch.execute( 117 | 'CREATE TABLE IF NOT EXISTS rewards(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, emoji TEXT NOT NULL);'); 118 | _defaultRewards.forEach((reward) { 119 | batch.insert('rewards', reward.toMap()); 120 | }); 121 | 122 | // Create and insert default activities into database. 123 | batch.execute( 124 | 'CREATE TABLE IF NOT EXISTS activities(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, description TEXT NOT NULL, emoji TEXT NOT NULL, credits INTEGER NOT NULL);'); 125 | _defaultActivities.forEach((activity) { 126 | batch.insert('activities', activity.toMap()); 127 | }); 128 | 129 | // Create challenges table. 130 | batch.execute( 131 | "CREATE TABLE IF NOT EXISTS challenges(id INTEGER PRIMARY KEY AUTOINCREMENT, initial_level INTEGER NOT NULL, ideal_level INTEGER NOT NULL, updated_level INTEGER, reward_id INTEGER NOT NULL, created_at INTEGER NOT NULL, remind_at INTEGER NOT NULL, condition TEXT NOT NULL, FOREIGN KEY(reward_id) REFERENCES rewards(id));"); 132 | 133 | // Create challenges_activities table. 134 | batch.execute( 135 | 'CREATE TABLE IF NOT EXISTS challenges_activities(id INTEGER PRIMARY KEY AUTOINCREMENT, challenge_id INTEGER NOT NULL, activity_id INTEGER NOT NULL)'); 136 | 137 | await batch.commit(); 138 | } catch (err) { 139 | throw err; 140 | } 141 | }, 142 | version: 2, 143 | ); 144 | } catch (err) { 145 | throw err; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:sqflite/sqflite.dart'; 6 | import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 7 | 8 | import './constants.dart' as Constants; 9 | import './screens/home_screen.dart'; 10 | import './screens/triage_screen.dart'; 11 | import './screens/challenge_screen.dart'; 12 | import './screens/reward_screen.dart'; 13 | import './screens/schedule_screen.dart'; 14 | import './screens/check_back_screen.dart'; 15 | import './screens/review_screen.dart'; 16 | import './providers/activities.dart'; 17 | import './providers/rewards.dart'; 18 | import './providers/challenges.dart'; 19 | import './database/database.dart'; 20 | import './widgets/loading_screen.dart'; 21 | 22 | final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = 23 | FlutterLocalNotificationsPlugin(); 24 | NotificationAppLaunchDetails notificationAppLaunchDetails; 25 | 26 | void main() async { 27 | WidgetsFlutterBinding.ensureInitialized(); 28 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); 29 | 30 | try { 31 | // Enforce portrait orientation. 32 | await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 33 | 34 | // Setup local notifications. 35 | notificationAppLaunchDetails = 36 | await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails(); 37 | final initializationSettingsAndroid = 38 | AndroidInitializationSettings('@mipmap/ic_launcher'); 39 | final initializationSettingsIOS = IOSInitializationSettings(); 40 | final initializationSettings = InitializationSettings( 41 | initializationSettingsAndroid, initializationSettingsIOS); 42 | await flutterLocalNotificationsPlugin.initialize( 43 | initializationSettings, 44 | ); 45 | 46 | runApp(App()); 47 | } catch (err) { 48 | throw err; 49 | } 50 | } 51 | 52 | class App extends StatefulWidget { 53 | @override 54 | _AppState createState() => _AppState(); 55 | } 56 | 57 | class _AppState extends State { 58 | var _isInit = true; 59 | Database _database; 60 | 61 | @override 62 | void didChangeDependencies() { 63 | super.didChangeDependencies(); 64 | if (_isInit) { 65 | connectToDB(Constants.DATABASE_NAME).then((database) { 66 | _database = database; 67 | }).catchError((err) { 68 | print(err); 69 | }).whenComplete(() { 70 | setState(() { 71 | _isInit = false; 72 | }); 73 | }); 74 | } 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | return !_isInit 80 | ? MultiProvider( 81 | providers: [ 82 | ChangeNotifierProvider( 83 | create: (_) => Activities(_database), 84 | ), 85 | ChangeNotifierProvider( 86 | create: (_) => Rewards(_database), 87 | ), 88 | ChangeNotifierProvider( 89 | create: (_) => Challenges(_database), 90 | ), 91 | ], 92 | child: Consumer( 93 | builder: (context, challenges, _) { 94 | return MaterialApp( 95 | title: 'Helm', 96 | debugShowCheckedModeBanner: false, 97 | theme: ThemeData( 98 | primaryColor: Constants.PRIMARY_COLOR, 99 | accentColor: Constants.ACCENT_COLOR, 100 | textTheme: GoogleFonts.karlaTextTheme( 101 | Theme.of(context).textTheme, 102 | ), 103 | ), 104 | home: challenges.latestChallenge == null 105 | ? HomeScreen() 106 | : CheckBackScreen( 107 | challenge: challenges.latestChallenge, 108 | ), 109 | routes: { 110 | TriageScreen.routeName: (ctx) => TriageScreen(), 111 | ChallengeScreen.routeName: (ctx) => ChallengeScreen(), 112 | RewardScreen.routeName: (ctx) => RewardScreen(), 113 | ScheduleScreen.routeName: (ctx) => 114 | ScheduleScreen(_scheduleNotification), 115 | ReviewScreen.routeName: (ctx) => ReviewScreen(), 116 | }, 117 | ); 118 | }, 119 | ), 120 | ) 121 | : MaterialApp( 122 | debugShowCheckedModeBanner: false, 123 | home: Container( 124 | color: Constants.PRIMARY_COLOR, 125 | child: LoadingScreen(), 126 | ), 127 | ); 128 | } 129 | } 130 | 131 | Future _scheduleNotification( 132 | DateTime scheduledDateTime, String title, String description) async { 133 | final androidPlatformChannelSpecifics = AndroidNotificationDetails( 134 | 'helm_id', 135 | 'helm', 136 | 'Remind people to check in after spending their credits', 137 | ); 138 | final iOSPlatformChannelSpecifics = 139 | IOSNotificationDetails(sound: 'slow_spring_board.aiff'); 140 | final platformChannelSpecifics = NotificationDetails( 141 | androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics); 142 | await flutterLocalNotificationsPlugin.schedule( 143 | 0, 144 | title, 145 | description, 146 | scheduledDateTime, 147 | platformChannelSpecifics, 148 | ); 149 | } 150 | -------------------------------------------------------------------------------- /lib/models/activity.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | enum Problem { Anxiety, Stress, Depression } 4 | 5 | class ProblemDetails { 6 | final Problem type; 7 | final String noun; 8 | final String adjective; 9 | 10 | ProblemDetails( 11 | this.type, 12 | this.noun, 13 | this.adjective, 14 | ); 15 | } 16 | 17 | ProblemDetails getProblemDetails(Problem type) { 18 | switch (type) { 19 | case Problem.Anxiety: 20 | return ProblemDetails(Problem.Anxiety, 'anxiety', 'anxious'); 21 | case Problem.Stress: 22 | return ProblemDetails(Problem.Stress, 'stress', 'stressed'); 23 | case Problem.Depression: 24 | return ProblemDetails(Problem.Depression, 'depression', 'depressed'); 25 | default: 26 | throw Exception('Problem type is not recognised.'); 27 | } 28 | } 29 | 30 | ProblemDetails getProblemDetailsByNoun(String noun) { 31 | switch (noun) { 32 | case 'anxiety': 33 | return getProblemDetails(Problem.Anxiety); 34 | case 'stress': 35 | return getProblemDetails(Problem.Stress); 36 | case 'depression': 37 | return getProblemDetails(Problem.Depression); 38 | default: 39 | throw Exception('Problem noun is not recognised.'); 40 | } 41 | } 42 | 43 | class Activity { 44 | final int id; 45 | final String name; 46 | final String description; 47 | final String emoji; 48 | final int credits; 49 | 50 | Activity({ 51 | @required this.id, 52 | @required this.name, 53 | @required this.description, 54 | @required this.emoji, 55 | @required this.credits, 56 | }); 57 | 58 | Map toMap() => { 59 | 'id': id, 60 | 'name': name, 61 | 'description': description, 62 | 'emoji': emoji, 63 | 'credits': credits, 64 | }; 65 | 66 | factory Activity.fromMap(Map map) => Activity( 67 | id: map['id'] as int, 68 | name: map['name'] as String, 69 | description: map['description'] as String, 70 | emoji: map['emoji'] as String, 71 | credits: map['credits'] as int, 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /lib/models/challenge.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | import './activity.dart'; 4 | import './reward.dart'; 5 | 6 | class Challenge { 7 | final int id; 8 | final int initialLevel; 9 | final int idealLevel; 10 | final int updatedLevel; 11 | final Set activities; 12 | final Reward reward; 13 | final DateTime remindAt; 14 | final ProblemDetails problem; 15 | 16 | Challenge({ 17 | this.id, 18 | @required this.initialLevel, 19 | @required this.idealLevel, 20 | this.updatedLevel, 21 | @required this.activities, 22 | @required this.reward, 23 | @required this.remindAt, 24 | @required this.problem, 25 | }); 26 | 27 | Map toMap() => { 28 | 'initial_level': initialLevel, 29 | 'ideal_level': idealLevel, 30 | 'updated_level': updatedLevel, 31 | 'reward_id': reward.id, 32 | 'remind_at': remindAt.millisecondsSinceEpoch, 33 | 'condition': problem.noun, 34 | }; 35 | 36 | factory Challenge.fromMap(Map map) => Challenge( 37 | id: map['id'], 38 | initialLevel: map['initial_level'], 39 | idealLevel: map['ideal_level'], 40 | updatedLevel: map['updated_level'], 41 | activities: 42 | (map['activities'] as List).map((a) => Activity.fromMap(a)).toSet(), 43 | reward: Reward.fromMap(map['reward']), 44 | remindAt: DateTime.fromMillisecondsSinceEpoch(map['remind_at']), 45 | problem: getProblemDetailsByNoun(map['condition']), 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /lib/models/reward.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class Reward { 4 | final int id; 5 | final String name; 6 | final String emoji; 7 | 8 | Reward({ 9 | @required this.id, 10 | @required this.name, 11 | @required this.emoji, 12 | }); 13 | 14 | Map toMap() => { 15 | 'id': id, 16 | 'name': name, 17 | 'emoji': emoji, 18 | }; 19 | 20 | factory Reward.fromMap(Map map) => Reward( 21 | id: map['id'] as int, 22 | name: map['name'] as String, 23 | emoji: map['emoji'] as String, 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /lib/providers/activities.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | import '../models/activity.dart'; 5 | 6 | class Activities with ChangeNotifier { 7 | final Database _database; 8 | List _activities = []; 9 | 10 | Activities(this._database); 11 | 12 | List get activities { 13 | return _activities.map((a) => a).toList(); 14 | } 15 | 16 | Future fetchAndSetActivities() async { 17 | try { 18 | final results = await _database.query('activities'); 19 | _activities = results.map((a) => Activity.fromMap(a)).toList(); 20 | notifyListeners(); 21 | } catch (err) { 22 | throw err; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/providers/challenges.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | import '../models/challenge.dart'; 5 | 6 | class Challenges with ChangeNotifier { 7 | final Database _database; 8 | Challenge _latestChallengeToday; 9 | 10 | Challenges(this._database) { 11 | fetchAndSetLatestChallenge(); 12 | } 13 | 14 | Challenge get latestChallenge { 15 | return _latestChallengeToday; 16 | } 17 | 18 | Future fetchAndSetLatestChallenge() async { 19 | try { 20 | final challengeResults = await _database.query( 21 | 'challenges', 22 | where: 23 | "DATE(created_at/1000, 'unixepoch', 'localtime')=Date('now', 'localtime') AND updated_level IS NULL", 24 | orderBy: 'created_at DESC', 25 | limit: 1, 26 | ); 27 | 28 | if (challengeResults.length < 1) { 29 | _latestChallengeToday = null; 30 | return; 31 | } 32 | 33 | final challengeId = challengeResults[0]['id'] as int; 34 | final activityResults = await _database.query( 35 | 'activities', 36 | where: 37 | 'id IN (SELECT activity_id FROM challenges_activities WHERE challenge_id=?)', 38 | whereArgs: [challengeId], 39 | ); 40 | 41 | final rewardResults = await _database.query( 42 | 'rewards', 43 | where: 'id=?', 44 | whereArgs: [challengeResults[0]['reward_id'] as int], 45 | ); 46 | 47 | _latestChallengeToday = Challenge.fromMap({ 48 | ...challengeResults[0], 49 | 'activities': activityResults, 50 | 'reward': rewardResults[0], 51 | }); 52 | 53 | notifyListeners(); 54 | } catch (err) { 55 | throw err; 56 | } 57 | } 58 | 59 | Future create(Challenge challenge) async { 60 | try { 61 | await _database.transaction((tx) async { 62 | var newChallengeId = 1; 63 | final mostRecentChallengeList = 64 | await tx.query('challenges', orderBy: 'id DESC', limit: 1); 65 | 66 | if (mostRecentChallengeList.length > 0) { 67 | newChallengeId = (mostRecentChallengeList[0]['id'] as int) + 1; 68 | } 69 | 70 | final batch = tx.batch(); 71 | 72 | final challengeToInsert = challenge.toMap(); 73 | challengeToInsert['id'] = newChallengeId; 74 | challengeToInsert['created_at'] = DateTime.now().millisecondsSinceEpoch; 75 | batch.insert('challenges', challengeToInsert); 76 | 77 | challenge.activities.forEach((a) { 78 | batch.insert('challenges_activities', { 79 | 'challenge_id': newChallengeId, 80 | 'activity_id': a.id, 81 | }); 82 | }); 83 | 84 | await batch.commit(); 85 | }); 86 | await fetchAndSetLatestChallenge(); 87 | notifyListeners(); 88 | } catch (err) { 89 | throw err; 90 | } 91 | } 92 | 93 | Future endChallenge(int challengeId, int updatedLevel) async { 94 | try { 95 | await _database.update( 96 | 'challenges', 97 | {'updated_level': updatedLevel}, 98 | where: 'id=?', 99 | whereArgs: [challengeId], 100 | ); 101 | await fetchAndSetLatestChallenge(); 102 | notifyListeners(); 103 | } catch (err) { 104 | throw err; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /lib/providers/rewards.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | import 'package:sqflite/sqflite.dart'; 3 | 4 | import '../models/reward.dart'; 5 | 6 | class Rewards with ChangeNotifier { 7 | final Database _database; 8 | List _rewards = []; 9 | 10 | Rewards(this._database); 11 | 12 | List get rewards { 13 | return _rewards.map((r) => r).toList(); 14 | } 15 | 16 | Future fetchAndSetRewards() async { 17 | try { 18 | final results = await _database.query('rewards'); 19 | _rewards = results.map((r) => Reward.fromMap(r)).toList(); 20 | notifyListeners(); 21 | } catch (err) { 22 | print(err); 23 | throw err; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/screens/challenge_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../widgets/action_panel.dart'; 6 | import '../widgets/emoji_button.dart'; 7 | import '../widgets/custom_safe_area.dart'; 8 | import './reward_screen.dart'; 9 | import '../models/activity.dart'; 10 | import '../providers/activities.dart'; 11 | 12 | class ChallengeScreen extends StatefulWidget { 13 | static const routeName = '/challenge'; 14 | 15 | @override 16 | _ChallengeScreenState createState() => _ChallengeScreenState(); 17 | } 18 | 19 | class _ChallengeScreenState extends State { 20 | var _isInit = true; 21 | ProblemDetails _problem; 22 | int _actualLevel; 23 | int _idealLevel; 24 | Set _selectedActivities = {}; 25 | 26 | @override 27 | void didChangeDependencies() { 28 | super.didChangeDependencies(); 29 | if (_isInit) { 30 | final params = 31 | ModalRoute.of(context).settings.arguments as Map; 32 | _problem = params['problem'] as ProblemDetails; 33 | _actualLevel = params['actualLevel'] as int; 34 | _idealLevel = params['idealLevel'] as int; 35 | 36 | _isInit = false; 37 | } 38 | } 39 | 40 | // Use only for non-empty strings. 41 | String _capitaliseFirstChar(String value) { 42 | return value[0].toUpperCase() + value.substring(1); 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | final theme = Theme.of(context); 48 | final equationProblemWord = _capitaliseFirstChar(_problem.noun); 49 | final activities = 50 | Provider.of(context, listen: false).activities; 51 | 52 | return CustomSafeArea( 53 | child: Scaffold( 54 | appBar: AppBar( 55 | backgroundColor: theme.primaryColor, 56 | elevation: 0.0, 57 | leading: IconButton( 58 | icon: Icon(Icons.arrow_back), 59 | onPressed: () => Navigator.of(context).pop(), 60 | ), 61 | centerTitle: false, 62 | // title: Text('Challenge'), 63 | ), 64 | body: Stack( 65 | children: [ 66 | LayoutBuilder( 67 | builder: (ctx, constraints) => Container( 68 | width: double.infinity, 69 | height: constraints.maxHeight - 67.0, 70 | padding: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0), 71 | child: Column( 72 | mainAxisSize: MainAxisSize.min, 73 | crossAxisAlignment: CrossAxisAlignment.start, 74 | children: [ 75 | Column( 76 | mainAxisSize: MainAxisSize.min, 77 | crossAxisAlignment: CrossAxisAlignment.start, 78 | children: [ 79 | Flexible( 80 | child: FittedBox( 81 | child: Text( 82 | 'The Challenge', 83 | style: const TextStyle( 84 | fontWeight: FontWeight.bold, 85 | fontSize: 20.0, 86 | letterSpacing: 1.2, 87 | ), 88 | ), 89 | ), 90 | ), 91 | SizedBox( 92 | height: 12.0, 93 | ), 94 | Flexible( 95 | child: Text( 96 | 'Your extra ${_problem.noun} is converted into credits.', 97 | style: const TextStyle( 98 | fontSize: 14, 99 | height: 1.2, 100 | ), 101 | ), 102 | ), 103 | SizedBox( 104 | height: 14.0, 105 | ), 106 | Flexible( 107 | child: Container( 108 | width: double.infinity, 109 | padding: const EdgeInsets.symmetric( 110 | vertical: 12.0, 111 | ), 112 | decoration: BoxDecoration( 113 | color: Colors.grey.shade200, 114 | borderRadius: BorderRadius.circular(5.0), 115 | ), 116 | child: Text( 117 | 'Current $equationProblemWord - Ideal $equationProblemWord \n= $equationProblemWord Credits', 118 | textAlign: TextAlign.center, 119 | style: GoogleFonts.robotoMono().copyWith( 120 | fontSize: 13.0, 121 | ), 122 | ), 123 | ), 124 | ), 125 | SizedBox( 126 | height: 14.0, 127 | ), 128 | Flexible( 129 | child: Text( 130 | 'Your objective is to spend all of it to bring your current ${_problem.noun} level down to an ideal level before the day ends.', 131 | style: const TextStyle( 132 | fontSize: 14.0, 133 | height: 1.2, 134 | ), 135 | ), 136 | ), 137 | ], 138 | ), 139 | SizedBox( 140 | height: 30.0, 141 | ), 142 | Expanded( 143 | child: Column( 144 | mainAxisSize: MainAxisSize.min, 145 | children: [ 146 | Row( 147 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 148 | children: [ 149 | Flexible( 150 | child: FittedBox( 151 | child: Text( 152 | 'Spend Credits On', 153 | style: const TextStyle( 154 | fontWeight: FontWeight.bold, 155 | letterSpacing: 1.1, 156 | fontSize: 16.0, 157 | ), 158 | ), 159 | ), 160 | ), 161 | Flexible( 162 | child: FittedBox( 163 | child: Text( 164 | '${_actualLevel - _idealLevel} $equationProblemWord Credits', 165 | style: const TextStyle( 166 | fontSize: 14.0, 167 | ), 168 | ), 169 | ), 170 | ), 171 | ], 172 | ), 173 | SizedBox( 174 | height: 15.0, 175 | ), 176 | Flexible( 177 | child: ListView.builder( 178 | itemCount: activities.length, 179 | itemBuilder: (ctx, index) { 180 | final activity = activities[index]; 181 | final isSelected = _selectedActivities 182 | .where((a) => a.id == activity.id) 183 | .length > 184 | 0; 185 | 186 | return Padding( 187 | padding: const EdgeInsets.symmetric( 188 | vertical: 4.0, 189 | ), 190 | child: EmojiButton( 191 | emoji: activity.emoji, 192 | title: activity.name, 193 | enableSubtitle: true, 194 | backgroundColor: isSelected 195 | ? Colors.greenAccent.shade100 196 | : null, 197 | outlineColor: isSelected 198 | ? Colors.greenAccent.shade200 199 | : null, 200 | subtitle: 201 | '${activity.credits} $equationProblemWord Credits', 202 | onPressed: () { 203 | if (isSelected) { 204 | setState(() { 205 | _selectedActivities.removeWhere( 206 | (a) => a.id == activity.id, 207 | ); 208 | }); 209 | } else { 210 | var selectedCredits = 0; 211 | 212 | if (_selectedActivities 213 | .isNotEmpty) { 214 | selectedCredits = 215 | _selectedActivities.fold( 216 | 0, 217 | (acc, a) => acc + a.credits, 218 | ); 219 | } 220 | 221 | if ((selectedCredits + 222 | activity.credits) <= 223 | (_actualLevel - _idealLevel)) { 224 | setState(() { 225 | _selectedActivities 226 | .add(activity); 227 | }); 228 | } else { 229 | showDialog( 230 | context: context, 231 | child: AlertDialog( 232 | title: Text('Oops!'), 233 | content: Text( 234 | 'You do not have sufficient ${_problem.noun} credits left.', 235 | ), 236 | actions: [ 237 | FlatButton( 238 | textColor: 239 | theme.accentColor, 240 | child: Text('Okay'), 241 | onPressed: () => 242 | Navigator.of(context) 243 | .pop(), 244 | ), 245 | ], 246 | ), 247 | ); 248 | } 249 | } 250 | }), 251 | ); 252 | }), 253 | ), 254 | ], 255 | ), 256 | ), 257 | ], 258 | ), 259 | ), 260 | ), 261 | Align( 262 | alignment: Alignment.bottomCenter, 263 | child: ActionPanel( 264 | title: 'Next', 265 | onPressed: () { 266 | var isValid = !(_selectedActivities.isEmpty || 267 | _selectedActivities.fold( 268 | 0, (acc, a) => acc + a.credits) != 269 | _actualLevel - _idealLevel); 270 | 271 | if (isValid) { 272 | Navigator.of(context).pushNamed( 273 | RewardScreen.routeName, 274 | arguments: { 275 | 'problem': _problem, 276 | 'actualLevel': _actualLevel, 277 | 'idealLevel': _idealLevel, 278 | 'activities': _selectedActivities, 279 | }, 280 | ); 281 | } else { 282 | showDialog( 283 | context: context, 284 | child: AlertDialog( 285 | title: Text('Oops!'), 286 | content: Text( 287 | 'It appears that you have not utilised all your ${_problem.noun} credits yet.', 288 | ), 289 | actions: [ 290 | FlatButton( 291 | child: Text('Okay'), 292 | textColor: theme.accentColor, 293 | onPressed: () => Navigator.of(context).pop(), 294 | ) 295 | ], 296 | ), 297 | ); 298 | } 299 | }, 300 | ), 301 | ), 302 | ], 303 | ), 304 | ), 305 | ); 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /lib/screens/check_back_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | 4 | import '../widgets/emoji_button.dart'; 5 | import '../widgets/custom_safe_area.dart'; 6 | import '../widgets/action_panel.dart'; 7 | import '../models/challenge.dart'; 8 | import './review_screen.dart'; 9 | 10 | class CheckBackScreen extends StatelessWidget { 11 | static const routeName = '/check-back'; 12 | final Challenge challenge; 13 | 14 | CheckBackScreen({ 15 | @required this.challenge, 16 | }); 17 | 18 | // Use only for non-empty strings. 19 | String _capitaliseFirstChar(String value) { 20 | return value[0].toUpperCase() + value.substring(1); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final theme = Theme.of(context); 26 | final creditsProblemWord = _capitaliseFirstChar(challenge.problem.noun); 27 | 28 | return CustomSafeArea( 29 | child: Scaffold( 30 | appBar: AppBar( 31 | backgroundColor: Colors.transparent, 32 | automaticallyImplyLeading: false, 33 | elevation: 0.0, 34 | ), 35 | body: Stack( 36 | children: [ 37 | LayoutBuilder( 38 | builder: (ctx, constraints) => Container( 39 | width: double.infinity, 40 | padding: const EdgeInsets.fromLTRB(80.0, 0.0, 0.0, 30.0), 41 | height: constraints.maxHeight - 67.0, 42 | child: Column( 43 | mainAxisSize: MainAxisSize.min, 44 | children: [ 45 | Flexible( 46 | child: SvgPicture.asset( 47 | 'assets/images/drive.svg', 48 | alignment: Alignment.bottomCenter, 49 | ), 50 | ), 51 | ], 52 | ), 53 | )), 54 | LayoutBuilder( 55 | builder: (ctx, constraints) => Container( 56 | width: double.infinity, 57 | height: constraints.maxHeight - 67.0, 58 | padding: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0), 59 | // color: theme.primaryColor, 60 | child: Column( 61 | mainAxisSize: MainAxisSize.min, 62 | crossAxisAlignment: CrossAxisAlignment.start, 63 | children: [ 64 | Column( 65 | mainAxisSize: MainAxisSize.min, 66 | crossAxisAlignment: CrossAxisAlignment.start, 67 | children: [ 68 | Flexible( 69 | child: FittedBox( 70 | child: Text( 71 | 'Objective', 72 | style: TextStyle( 73 | fontWeight: FontWeight.bold, 74 | fontSize: 25.0, 75 | letterSpacing: 1.2, 76 | ), 77 | ), 78 | ), 79 | ), 80 | SizedBox( 81 | height: 10.0, 82 | ), 83 | Flexible( 84 | child: Text( 85 | 'Spend ${challenge.initialLevel - challenge.idealLevel} ${challenge.problem.noun} credits on', 86 | style: const TextStyle( 87 | fontSize: 14, 88 | height: 1.2, 89 | ), 90 | ), 91 | ), 92 | ], 93 | ), 94 | SizedBox( 95 | height: 15.0, 96 | ), 97 | Expanded( 98 | child: ListView.builder( 99 | itemCount: challenge.activities.length, 100 | itemBuilder: (ctx, index) { 101 | final activity = 102 | challenge.activities.toList()[index]; 103 | return Padding( 104 | padding: const EdgeInsets.symmetric( 105 | vertical: 4.0, 106 | ), 107 | child: EmojiButton( 108 | emoji: activity.emoji, 109 | title: activity.name, 110 | enableSubtitle: true, 111 | subtitle: 112 | '${activity.credits} $creditsProblemWord Credits', 113 | backgroundColor: Colors.white, 114 | outlineColor: Colors.white, 115 | onPressed: () {}, 116 | ), 117 | ); 118 | }), 119 | ), 120 | ], 121 | ), 122 | ), 123 | ), 124 | Align( 125 | alignment: Alignment.bottomCenter, 126 | child: ActionPanel( 127 | title: 'I\'m Done!', 128 | onPressed: () { 129 | showDialog( 130 | context: context, 131 | barrierDismissible: false, 132 | child: AlertDialog( 133 | title: Text('Great Job ${challenge.reward.emoji}'), 134 | content: Text( 135 | 'Reward yourself now with a/an ${challenge.reward.name.toLowerCase()} for the hard work you\'ve put in!', 136 | ), 137 | actions: [ 138 | FlatButton( 139 | child: Text('Okay'), 140 | textColor: theme.accentColor, 141 | onPressed: () { 142 | Navigator.of(context).pop(); 143 | Navigator.of(context).pushNamed( 144 | ReviewScreen.routeName, 145 | arguments: challenge, 146 | ); 147 | }), 148 | ], 149 | ), 150 | ); 151 | }, 152 | ), 153 | ), 154 | ], 155 | ), 156 | ), 157 | ); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /lib/screens/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_svg/flutter_svg.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../widgets/emoji_button.dart'; 6 | import '../screens/triage_screen.dart'; 7 | import '../widgets/custom_safe_area.dart'; 8 | import '../models/activity.dart'; 9 | import '../providers/activities.dart'; 10 | import '../providers/rewards.dart'; 11 | 12 | class HomeScreen extends StatefulWidget { 13 | @override 14 | _HomeScreenState createState() => _HomeScreenState(); 15 | } 16 | 17 | class _HomeScreenState extends State { 18 | var _isInit = true; 19 | 20 | @override 21 | void didChangeDependencies() { 22 | super.didChangeDependencies(); 23 | if (_isInit) { 24 | Provider.of(context, listen: false) 25 | .fetchAndSetActivities() 26 | .then((_) { 27 | return Provider.of(context, listen: false) 28 | .fetchAndSetRewards(); 29 | }) 30 | .catchError((err) => showDialog( 31 | context: context, 32 | child: AlertDialog( 33 | title: Text('Oops!'), 34 | content: Text( 35 | 'An unexpected error occurred when initialising data.', 36 | ), 37 | actions: [ 38 | FlatButton( 39 | textColor: Theme.of(context).accentColor, 40 | child: Text( 41 | 'Okay', 42 | ), 43 | onPressed: () => Navigator.of(context).pop(), 44 | ), 45 | ], 46 | ), 47 | )) 48 | .whenComplete(() { 49 | _isInit = false; 50 | }); 51 | } 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return CustomSafeArea( 57 | child: Scaffold( 58 | body: Container( 59 | width: double.infinity, 60 | color: Theme.of(context).primaryColor, 61 | child: Column( 62 | children: [ 63 | Expanded( 64 | flex: 2, 65 | child: Padding( 66 | padding: const EdgeInsets.symmetric( 67 | horizontal: 20.0, 68 | ), 69 | child: Column( 70 | mainAxisAlignment: MainAxisAlignment.end, 71 | crossAxisAlignment: CrossAxisAlignment.start, 72 | children: [ 73 | Flexible( 74 | child: Padding( 75 | padding: const EdgeInsets.symmetric( 76 | horizontal: 4.0, 77 | ), 78 | child: SvgPicture.asset( 79 | 'assets/images/yoga.svg', 80 | alignment: Alignment.bottomCenter, 81 | ), 82 | ), 83 | ), 84 | Flexible( 85 | child: Container( 86 | margin: const EdgeInsets.symmetric( 87 | vertical: 20.0, 88 | ), 89 | child: FittedBox( 90 | child: Text( 91 | 'I\'m feeling . . .', 92 | style: TextStyle( 93 | fontWeight: FontWeight.bold, 94 | letterSpacing: 1.2, 95 | fontSize: 24.0, 96 | ), 97 | ), 98 | ), 99 | ), 100 | ), 101 | ], 102 | ), 103 | ), 104 | ), 105 | Flexible( 106 | flex: 1, 107 | child: Container( 108 | width: double.infinity, 109 | padding: const EdgeInsets.symmetric( 110 | horizontal: 10.0, 111 | vertical: 2.0, 112 | ), 113 | color: Colors.white, 114 | child: Column( 115 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 116 | children: [ 117 | EmojiButton( 118 | height: 50.0, 119 | emoji: '😰', 120 | title: 'Anxious', 121 | onPressed: () => Navigator.of(context).pushNamed( 122 | TriageScreen.routeName, 123 | arguments: { 124 | 'problem': Problem.Anxiety 125 | }, 126 | ), 127 | ), 128 | EmojiButton( 129 | height: 50.0, 130 | emoji: '😫', 131 | title: 'Stressed', 132 | onPressed: () => Navigator.of(context).pushNamed( 133 | TriageScreen.routeName, 134 | arguments: { 135 | 'problem': Problem.Stress, 136 | }, 137 | ), 138 | ), 139 | EmojiButton( 140 | height: 50.0, 141 | emoji: '😞', 142 | title: 'Depressed', 143 | onPressed: () => Navigator.of(context).pushNamed( 144 | TriageScreen.routeName, 145 | arguments: { 146 | 'problem': Problem.Depression, 147 | }, 148 | ), 149 | ), 150 | ], 151 | ), 152 | ), 153 | ), 154 | ], 155 | ), 156 | ), 157 | ), 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/screens/review_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | import '../widgets/custom_safe_area.dart'; 5 | import '../widgets/action_panel.dart'; 6 | import '../widgets/knob_slider.dart'; 7 | import '../models/challenge.dart'; 8 | import '../providers/challenges.dart'; 9 | import './triage_screen.dart'; 10 | 11 | class ReviewScreen extends StatefulWidget { 12 | static const routeName = '/review'; 13 | 14 | @override 15 | _ReviewScreenState createState() => _ReviewScreenState(); 16 | } 17 | 18 | class _ReviewScreenState extends State { 19 | var _isInit = true; 20 | Challenge _challenge; 21 | var _updatedLevel = 0; 22 | 23 | @override 24 | void didChangeDependencies() { 25 | super.didChangeDependencies(); 26 | if (_isInit) { 27 | _challenge = ModalRoute.of(context).settings.arguments as Challenge; 28 | _isInit = false; 29 | } 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | final theme = Theme.of(context); 35 | 36 | return CustomSafeArea( 37 | child: Scaffold( 38 | appBar: AppBar( 39 | backgroundColor: theme.primaryColor, 40 | elevation: 0.0, 41 | leading: IconButton( 42 | icon: Icon(Icons.arrow_back), 43 | onPressed: () => Navigator.of(context).pop(), 44 | ), 45 | ), 46 | body: Stack( 47 | children: [ 48 | LayoutBuilder( 49 | builder: (ctx, constraints) => Container( 50 | width: double.infinity, 51 | height: constraints.maxHeight - 67.0, 52 | padding: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0), 53 | child: Column( 54 | mainAxisSize: MainAxisSize.min, 55 | crossAxisAlignment: CrossAxisAlignment.start, 56 | children: [ 57 | Column( 58 | mainAxisSize: MainAxisSize.min, 59 | crossAxisAlignment: CrossAxisAlignment.start, 60 | children: [ 61 | Flexible( 62 | child: FittedBox( 63 | child: Text( 64 | 'Review', 65 | style: const TextStyle( 66 | fontWeight: FontWeight.bold, 67 | fontSize: 20.0, 68 | letterSpacing: 1.2, 69 | ), 70 | ), 71 | ), 72 | ), 73 | SizedBox( 74 | height: 12.0, 75 | ), 76 | Flexible( 77 | child: Text( 78 | 'Please rate your ${_challenge.problem.noun} levels after having spent your ${_challenge.problem.noun} credits.', 79 | style: const TextStyle( 80 | fontSize: 14, 81 | height: 1.2, 82 | ), 83 | ), 84 | ), 85 | ], 86 | ), 87 | SizedBox( 88 | height: 15.0, 89 | ), 90 | Flexible( 91 | fit: FlexFit.loose, 92 | child: Column( 93 | mainAxisSize: MainAxisSize.min, 94 | children: [ 95 | KnobSlider( 96 | min: 0, 97 | max: 10, 98 | initialValue: 0, 99 | onChanged: (value) { 100 | _updatedLevel = value; 101 | return true; 102 | }, 103 | ), 104 | ], 105 | ), 106 | ), 107 | ], 108 | ), 109 | ), 110 | ), 111 | Align( 112 | alignment: Alignment.bottomCenter, 113 | child: ActionPanel( 114 | title: 'Next', 115 | onPressed: () async { 116 | try { 117 | await Provider.of(context, listen: false) 118 | .endChallenge(_challenge.id, _updatedLevel); 119 | 120 | var dialogMessage = ''; 121 | var dialogActions = []; 122 | final okayButton = FlatButton( 123 | child: Text('Okay'), 124 | textColor: theme.accentColor, 125 | onPressed: () => 126 | Navigator.of(context).pushNamedAndRemoveUntil( 127 | '/', 128 | (route) => false, 129 | ), 130 | ); 131 | final tryAgainButton = FlatButton( 132 | child: Text('Try Again'), 133 | textColor: theme.accentColor, 134 | onPressed: () { 135 | Navigator.of(context).pop(); 136 | Navigator.of(context).pushReplacementNamed( 137 | TriageScreen.routeName, 138 | arguments: { 139 | 'problem': _challenge.problem.type, 140 | 'initialLevel': _updatedLevel, 141 | }, 142 | ); 143 | }, 144 | ); 145 | final takeABreakButton = FlatButton( 146 | child: Text('Take a Break'), 147 | textColor: theme.accentColor, 148 | onPressed: () => 149 | Navigator.of(context).pushNamedAndRemoveUntil( 150 | '/', 151 | (route) => false, 152 | ), 153 | ); 154 | 155 | if (_updatedLevel < 1) { 156 | dialogMessage = 157 | 'Congratulations on getting your ${_challenge.problem.noun} levels down to zero! You rock 🤘'; 158 | dialogActions.add(okayButton); 159 | } else if (_updatedLevel < _challenge.idealLevel) { 160 | dialogMessage = 161 | 'Your ${_challenge.problem.noun} level seems better than ideal, keep up the good work!'; 162 | dialogActions.addAll([takeABreakButton, tryAgainButton]); 163 | } else if (_updatedLevel == _challenge.idealLevel) { 164 | dialogMessage = 165 | 'Good job spending all your ${_challenge.problem.noun} credits, you\'re getting better!'; 166 | dialogActions.addAll([takeABreakButton, tryAgainButton]); 167 | } else if (_updatedLevel > _challenge.idealLevel && 168 | _updatedLevel < _challenge.initialLevel) { 169 | dialogMessage = 170 | 'Don\'t give up, you made great effort spending your ${_challenge.problem.noun} credits!'; 171 | dialogActions.addAll([takeABreakButton, tryAgainButton]); 172 | } else if (_updatedLevel < 7) { 173 | dialogMessage = 174 | 'We take some steps forward, some steps backwards, but most importantly, we\'re progressing!'; 175 | dialogActions.addAll([takeABreakButton, tryAgainButton]); 176 | } else { 177 | dialogMessage = 178 | 'Progress takes time, you\'re putting in effort and that\'s all that matters! Do talk to someone if your ${_challenge.problem.noun} persists 💪'; 179 | dialogActions.addAll([takeABreakButton, tryAgainButton]); 180 | } 181 | 182 | showDialog( 183 | context: context, 184 | barrierDismissible: false, 185 | builder: (ctx) => AlertDialog( 186 | title: Text('Review'), 187 | content: Text( 188 | dialogMessage, 189 | style: TextStyle( 190 | height: 1.3, 191 | ), 192 | ), 193 | actions: dialogActions, 194 | ), 195 | ); 196 | } catch (err) { 197 | showDialog( 198 | context: context, 199 | child: AlertDialog( 200 | title: Text('Oops!'), 201 | content: Text('An unexpected error occurred.'), 202 | actions: [ 203 | FlatButton( 204 | child: Text('Okay'), 205 | textColor: theme.accentColor, 206 | onPressed: () => Navigator.of(context).pop(), 207 | ) 208 | ], 209 | ), 210 | ); 211 | } 212 | }, 213 | ), 214 | ), 215 | ], 216 | ), 217 | ), 218 | ); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /lib/screens/reward_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | import '../widgets/action_panel.dart'; 5 | import '../widgets/custom_safe_area.dart'; 6 | import '../widgets/emoji_button.dart'; 7 | import '../screens/schedule_screen.dart'; 8 | import '../models/activity.dart'; 9 | import '../models/reward.dart'; 10 | import '../providers/rewards.dart'; 11 | 12 | class RewardScreen extends StatefulWidget { 13 | static const routeName = '/reward'; 14 | 15 | @override 16 | _RewardScreenState createState() => _RewardScreenState(); 17 | } 18 | 19 | class _RewardScreenState extends State { 20 | var _isInit = true; 21 | ProblemDetails _problem; 22 | int _actualLevel; 23 | int _idealLevel; 24 | Set _selectedActivities = {}; 25 | Reward _selectedReward; 26 | 27 | @override 28 | void didChangeDependencies() { 29 | super.didChangeDependencies(); 30 | if (_isInit) { 31 | final params = 32 | ModalRoute.of(context).settings.arguments as Map; 33 | _problem = params['problem'] as ProblemDetails; 34 | _actualLevel = params['actualLevel'] as int; 35 | _idealLevel = params['idealLevel'] as int; 36 | _selectedActivities = params['activities'] as Set; 37 | 38 | _isInit = false; 39 | } 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | final theme = Theme.of(context); 45 | final rewards = Provider.of(context, listen: false).rewards; 46 | 47 | return CustomSafeArea( 48 | child: Scaffold( 49 | appBar: AppBar( 50 | backgroundColor: theme.primaryColor, 51 | elevation: 0.0, 52 | leading: IconButton( 53 | icon: Icon(Icons.arrow_back), 54 | onPressed: () => Navigator.of(context).pop(), 55 | ), 56 | ), 57 | body: Stack( 58 | children: [ 59 | LayoutBuilder( 60 | builder: (ctx, constraints) => Container( 61 | width: double.infinity, 62 | height: constraints.maxHeight - 67.0, 63 | padding: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0), 64 | child: Column( 65 | mainAxisSize: MainAxisSize.min, 66 | crossAxisAlignment: CrossAxisAlignment.start, 67 | children: [ 68 | Column( 69 | mainAxisSize: MainAxisSize.min, 70 | crossAxisAlignment: CrossAxisAlignment.start, 71 | children: [ 72 | Flexible( 73 | child: FittedBox( 74 | child: Text( 75 | 'Reward', 76 | style: const TextStyle( 77 | fontWeight: FontWeight.bold, 78 | fontSize: 20.0, 79 | letterSpacing: 1.2, 80 | ), 81 | ), 82 | ), 83 | ), 84 | SizedBox( 85 | height: 12.0, 86 | ), 87 | Flexible( 88 | child: Text( 89 | 'We encourage you to pick a reward to award yourself with after you’ve spent your ${_problem.noun} credits. This would reinforce the positive habit of dealing with ${_problem.noun} in an actionable manner.', 90 | style: const TextStyle( 91 | fontSize: 14, 92 | height: 1.2, 93 | ), 94 | ), 95 | ), 96 | ], 97 | ), 98 | SizedBox( 99 | height: 30.0, 100 | ), 101 | Expanded( 102 | child: Column( 103 | mainAxisSize: MainAxisSize.min, 104 | children: [ 105 | Row( 106 | children: [ 107 | Flexible( 108 | fit: FlexFit.loose, 109 | child: FittedBox( 110 | child: Text( 111 | 'Pick a Reward', 112 | style: const TextStyle( 113 | fontWeight: FontWeight.bold, 114 | letterSpacing: 1.1, 115 | fontSize: 16.0, 116 | ), 117 | ), 118 | ), 119 | ), 120 | ], 121 | ), 122 | SizedBox( 123 | height: 15.0, 124 | ), 125 | Flexible( 126 | child: ListView.builder( 127 | itemCount: rewards.length, 128 | itemBuilder: (ctx, index) { 129 | final reward = rewards[index]; 130 | final isSelected = _selectedReward != null && 131 | reward.id == _selectedReward.id; 132 | 133 | return Padding( 134 | padding: const EdgeInsets.symmetric( 135 | vertical: 4.0, 136 | ), 137 | child: EmojiButton( 138 | emoji: reward.emoji, 139 | title: reward.name, 140 | backgroundColor: isSelected 141 | ? Colors.greenAccent.shade100 142 | : null, 143 | outlineColor: isSelected 144 | ? Colors.greenAccent.shade200 145 | : null, 146 | onPressed: () { 147 | setState(() { 148 | _selectedReward = reward; 149 | }); 150 | }, 151 | ), 152 | ); 153 | }), 154 | ), 155 | ], 156 | ), 157 | ), 158 | ], 159 | ), 160 | ), 161 | ), 162 | Align( 163 | alignment: Alignment.bottomCenter, 164 | child: ActionPanel( 165 | title: 'Next', 166 | onPressed: () { 167 | if (_selectedReward != null) { 168 | Navigator.of(context).pushNamed( 169 | ScheduleScreen.routeName, 170 | arguments: { 171 | 'problem': _problem, 172 | 'actualLevel': _actualLevel, 173 | 'idealLevel': _idealLevel, 174 | 'activities': _selectedActivities, 175 | 'reward': _selectedReward, 176 | }, 177 | ); 178 | } else { 179 | showDialog( 180 | context: context, 181 | child: AlertDialog( 182 | title: Text('Oops!'), 183 | content: Text( 184 | 'Positive reinforcement is important for making a good habit of dealing with ${_problem.noun} in an actionable manner. Please pick a reward!', 185 | ), 186 | actions: [ 187 | FlatButton( 188 | child: Text('Okay'), 189 | textColor: theme.accentColor, 190 | onPressed: () => Navigator.of(context).pop(), 191 | ), 192 | ], 193 | ), 194 | ); 195 | } 196 | }), 197 | ), 198 | ], 199 | ), 200 | ), 201 | ); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /lib/screens/schedule_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:intl/intl.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import '../widgets/custom_safe_area.dart'; 6 | import '../widgets/action_panel.dart'; 7 | import '../widgets/emoji_button.dart'; 8 | import '../models/activity.dart'; 9 | import '../models/reward.dart'; 10 | import '../models/challenge.dart'; 11 | import '../providers/challenges.dart'; 12 | 13 | class ScheduleScreen extends StatefulWidget { 14 | static const routeName = '/schedule'; 15 | final Future Function(DateTime, String, String) scheduleNotification; 16 | 17 | ScheduleScreen(this.scheduleNotification); 18 | 19 | @override 20 | _ScheduleScreenState createState() => _ScheduleScreenState(); 21 | } 22 | 23 | class _ScheduleScreenState extends State { 24 | var _isInit = true; 25 | ProblemDetails _problem; 26 | int _actualLevel; 27 | int _idealLevel; 28 | Set _selectedActivities = {}; 29 | Reward _selectedReward; 30 | 31 | TimeOfDay _scheduledTime = TimeOfDay( 32 | hour: TimeOfDay.now().hour + 1, 33 | minute: TimeOfDay.now().minute, 34 | ); 35 | 36 | @override 37 | void didChangeDependencies() { 38 | super.didChangeDependencies(); 39 | if (_isInit) { 40 | final params = 41 | ModalRoute.of(context).settings.arguments as Map; 42 | _problem = params['problem'] as ProblemDetails; 43 | _actualLevel = params['actualLevel'] as int; 44 | _idealLevel = params['idealLevel'] as int; 45 | _selectedActivities = params['activities'] as Set; 46 | _selectedReward = params['reward'] as Reward; 47 | 48 | _isInit = false; 49 | } 50 | } 51 | 52 | String _formatTimeOfDay(TimeOfDay time) { 53 | final today = DateTime.now(); 54 | final dateTime = 55 | DateTime(today.year, today.month, today.day, time.hour, time.minute); 56 | return DateFormat.jm().format(dateTime); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | final theme = Theme.of(context); 62 | final _timePickerThemeData = ThemeData( 63 | primaryColor: theme.primaryColor, 64 | accentColor: theme.accentColor, 65 | colorScheme: ColorScheme( 66 | background: Colors.white, 67 | brightness: Brightness.light, 68 | error: Colors.red, 69 | onBackground: Colors.black87, 70 | onError: Colors.red, 71 | onSurface: Colors.black87, 72 | onSecondary: Colors.black87, 73 | onPrimary: Colors.black, 74 | primary: theme.accentColor, 75 | primaryVariant: theme.accentColor, 76 | secondary: Colors.black, 77 | secondaryVariant: Colors.black, 78 | surface: Colors.white, 79 | ), 80 | ); 81 | 82 | return CustomSafeArea( 83 | child: Scaffold( 84 | appBar: AppBar( 85 | backgroundColor: theme.primaryColor, 86 | elevation: 0.0, 87 | leading: IconButton( 88 | icon: Icon(Icons.arrow_back), 89 | onPressed: () => Navigator.of(context).pop(), 90 | ), 91 | ), 92 | body: Stack( 93 | children: [ 94 | LayoutBuilder( 95 | builder: (ctx, constraints) => Container( 96 | width: double.infinity, 97 | height: constraints.maxHeight - 67.0, 98 | padding: const EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 0.0), 99 | child: Column( 100 | mainAxisSize: MainAxisSize.min, 101 | crossAxisAlignment: CrossAxisAlignment.start, 102 | children: [ 103 | Column( 104 | mainAxisSize: MainAxisSize.min, 105 | crossAxisAlignment: CrossAxisAlignment.start, 106 | children: [ 107 | Flexible( 108 | child: FittedBox( 109 | child: Text( 110 | 'Check Back With Us', 111 | style: const TextStyle( 112 | fontWeight: FontWeight.bold, 113 | fontSize: 20.0, 114 | letterSpacing: 1.2, 115 | ), 116 | ), 117 | ), 118 | ), 119 | SizedBox( 120 | height: 12.0, 121 | ), 122 | Flexible( 123 | child: Text( 124 | 'Set a time today to spend your ${_problem.noun} credits by and check back in with us! We’ll help to re-evaluate your ${_problem.noun} levels again.', 125 | style: const TextStyle( 126 | fontSize: 14, 127 | height: 1.2, 128 | ), 129 | ), 130 | ), 131 | ], 132 | ), 133 | SizedBox( 134 | height: 30.0, 135 | ), 136 | Column( 137 | mainAxisSize: MainAxisSize.min, 138 | crossAxisAlignment: CrossAxisAlignment.start, 139 | children: [ 140 | Row( 141 | children: [ 142 | Flexible( 143 | fit: FlexFit.loose, 144 | child: FittedBox( 145 | child: Text( 146 | 'Set a Time Today', 147 | style: const TextStyle( 148 | fontWeight: FontWeight.bold, 149 | letterSpacing: 1.1, 150 | fontSize: 16.0, 151 | ), 152 | ), 153 | ), 154 | ), 155 | ], 156 | ), 157 | SizedBox( 158 | height: 15.0, 159 | ), 160 | EmojiButton( 161 | emoji: '📅', 162 | title: _formatTimeOfDay(_scheduledTime), 163 | subtitle: 'Select to change time', 164 | enableSubtitle: true, 165 | onPressed: () async { 166 | try { 167 | final selectedTime = await showTimePicker( 168 | context: context, 169 | builder: (ctx, child) => Theme( 170 | data: _timePickerThemeData, 171 | child: child, 172 | ), 173 | initialTime: _scheduledTime, 174 | ); 175 | 176 | if (selectedTime == null) return; 177 | 178 | if (selectedTime.hour < TimeOfDay.now().hour || 179 | (selectedTime.hour == TimeOfDay.now().hour && 180 | selectedTime.minute <= 181 | TimeOfDay.now().minute + 5)) { 182 | final minimumTimeOfDay = _formatTimeOfDay( 183 | TimeOfDay( 184 | hour: TimeOfDay.now().hour, 185 | minute: TimeOfDay.now().minute + 5)); 186 | showDialog( 187 | context: context, 188 | child: AlertDialog( 189 | title: Text('Oops!'), 190 | content: Text( 191 | 'Please schedule for a time past $minimumTimeOfDay today.'), 192 | actions: [ 193 | FlatButton( 194 | textColor: theme.accentColor, 195 | child: Text('Okay'), 196 | onPressed: () => 197 | Navigator.of(context).pop(), 198 | ) 199 | ], 200 | ), 201 | ); 202 | } else { 203 | setState(() { 204 | _scheduledTime = selectedTime; 205 | }); 206 | } 207 | } catch (err) { 208 | showDialog( 209 | context: context, 210 | child: AlertDialog( 211 | title: Text( 212 | 'Oops!', 213 | ), 214 | content: 215 | Text('An unexpected error occurred!'), 216 | actions: [ 217 | FlatButton( 218 | textColor: theme.accentColor, 219 | child: Text( 220 | 'Okay', 221 | ), 222 | onPressed: () => 223 | Navigator.of(context).pop(), 224 | ), 225 | ], 226 | ), 227 | ); 228 | } 229 | }, 230 | ), 231 | ], 232 | ) 233 | ], 234 | ), 235 | ), 236 | ), 237 | Align( 238 | alignment: Alignment.bottomCenter, 239 | child: ActionPanel( 240 | title: 'Start Challenge', 241 | onPressed: () { 242 | final timeNow = TimeOfDay.now(); 243 | final isValid = !(_scheduledTime.hour < timeNow.hour || 244 | (_scheduledTime.hour == timeNow.hour && 245 | _scheduledTime.minute <= timeNow.minute)); 246 | if (isValid) { 247 | final dateTimeNow = DateTime.now(); 248 | final reminderDateTime = DateTime( 249 | dateTimeNow.year, 250 | dateTimeNow.month, 251 | dateTimeNow.day, 252 | _scheduledTime.hour, 253 | _scheduledTime.minute, 254 | ); 255 | 256 | final newChallenge = Challenge( 257 | initialLevel: _actualLevel, 258 | idealLevel: _idealLevel, 259 | activities: _selectedActivities, 260 | reward: _selectedReward, 261 | remindAt: reminderDateTime, 262 | problem: _problem, 263 | ); 264 | 265 | Provider.of(context, listen: false) 266 | .create(newChallenge) 267 | .then((_) { 268 | return widget.scheduleNotification( 269 | reminderDateTime, 'Check In', 'Let\'s re-evaluate your ${_problem.noun} levels again!'); 270 | }).then((_) { 271 | Navigator.of(context).pushNamedAndRemoveUntil( 272 | '/', 273 | (route) => false, 274 | ); 275 | }).catchError((err) { 276 | print(err); 277 | showDialog( 278 | context: context, 279 | child: AlertDialog( 280 | title: Text('Oops!'), 281 | content: Text('An unexpected error occurred!'), 282 | actions: [ 283 | FlatButton( 284 | child: Text('Okay'), 285 | textColor: theme.accentColor, 286 | onPressed: () => Navigator.of(context).pop(), 287 | ), 288 | ], 289 | ), 290 | ); 291 | }); 292 | } else { 293 | showDialog( 294 | context: context, 295 | child: AlertDialog( 296 | title: Text('Oops!'), 297 | content: Text( 298 | 'It appears that you\'ve picked a time that is over, please pick another time to check back in with us.', 299 | ), 300 | actions: [ 301 | FlatButton( 302 | child: Text('Okay'), 303 | textColor: theme.accentColor, 304 | onPressed: () => Navigator.of(context).pop(), 305 | ), 306 | ], 307 | ), 308 | ); 309 | } 310 | }, 311 | ), 312 | ), 313 | ], 314 | ), 315 | ), 316 | ); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /lib/screens/triage_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../widgets/slider_question.dart'; 4 | import '../widgets/action_panel.dart'; 5 | import '../widgets/custom_safe_area.dart'; 6 | import '../screens/challenge_screen.dart'; 7 | import '../models/activity.dart'; 8 | 9 | class TriageScreen extends StatefulWidget { 10 | static const routeName = '/triage'; 11 | 12 | @override 13 | _TriageScreenState createState() => _TriageScreenState(); 14 | } 15 | 16 | class _TriageScreenState extends State { 17 | var _selectedActualLevel = 1; 18 | var _selectedIdealLevel = 0; 19 | var _initialLevel = 1; 20 | ProblemDetails _problem; 21 | var _isInit = true; 22 | 23 | @override 24 | void didChangeDependencies() { 25 | super.didChangeDependencies(); 26 | if (_isInit) { 27 | final params = 28 | ModalRoute.of(context).settings.arguments as Map; 29 | _problem = getProblemDetails(params['problem'] as Problem); 30 | if (params.containsKey('initialLevel')) { 31 | _initialLevel = params['initialLevel'] as int; 32 | setState(() { 33 | _selectedActualLevel = _initialLevel; 34 | }); 35 | } 36 | _isInit = false; 37 | } 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | final theme = Theme.of(context); 43 | 44 | return CustomSafeArea( 45 | child: Scaffold( 46 | appBar: AppBar( 47 | backgroundColor: theme.primaryColor, 48 | elevation: 0.0, 49 | leading: IconButton( 50 | icon: Icon(Icons.arrow_back), 51 | onPressed: () => Navigator.of(context).pop(), 52 | ), 53 | ), 54 | body: Stack( 55 | children: [ 56 | Container( 57 | width: double.infinity, 58 | padding: const EdgeInsets.symmetric( 59 | vertical: 20.0, 60 | horizontal: 20.0, 61 | ), 62 | child: Column( 63 | mainAxisSize: MainAxisSize.min, 64 | children: [ 65 | Flexible( 66 | child: SliderQuestion( 67 | question: 'How ${_problem.adjective} are you?', 68 | min: 1, 69 | max: 10, 70 | initialValue: _initialLevel, 71 | onChanged: (value) { 72 | if (_selectedIdealLevel > value) { 73 | showDialog( 74 | context: context, 75 | builder: (ctx) => AlertDialog( 76 | title: Text('Oops!'), 77 | content: Text( 78 | 'Your ideal ${_problem.noun} level cannot be more than your actual ${_problem.noun} level.'), 79 | actions: [ 80 | FlatButton( 81 | textColor: theme.accentColor, 82 | child: Text( 83 | 'Okay', 84 | style: TextStyle( 85 | fontWeight: FontWeight.bold, 86 | ), 87 | ), 88 | onPressed: () => Navigator.of(context).pop(), 89 | ) 90 | ], 91 | ), 92 | ); 93 | return false; 94 | } else { 95 | setState(() { 96 | _selectedActualLevel = value; 97 | }); 98 | return true; 99 | } 100 | }, 101 | ), 102 | ), 103 | SizedBox( 104 | height: 40.0, 105 | ), 106 | Flexible( 107 | child: SliderQuestion( 108 | question: 'Pick an ideal ${_problem.noun} level', 109 | min: 0, 110 | max: _selectedActualLevel, 111 | initialValue: 0, 112 | onChanged: (value) { 113 | setState(() { 114 | _selectedIdealLevel = value; 115 | }); 116 | return true; 117 | }), 118 | ), 119 | ], 120 | ), 121 | ), 122 | Align( 123 | alignment: Alignment.bottomCenter, 124 | child: ActionPanel( 125 | title: 'Next', 126 | onPressed: () { 127 | if (_selectedIdealLevel >= _selectedActualLevel) { 128 | showDialog( 129 | barrierDismissible: false, 130 | context: context, 131 | builder: (ctx) => AlertDialog( 132 | title: Text('Oops!'), 133 | content: Text( 134 | 'Please pick an ideal ${_problem.noun} level that is realistically lower than your actual ${_problem.noun} level.', 135 | ), 136 | actions: [ 137 | FlatButton( 138 | child: Text('Okay'), 139 | textColor: theme.accentColor, 140 | onPressed: () => Navigator.of(context).pop(), 141 | ), 142 | ], 143 | ), 144 | ); 145 | } else { 146 | Navigator.of(context).pushNamed( 147 | ChallengeScreen.routeName, 148 | arguments: { 149 | 'problem': _problem, 150 | 'actualLevel': _selectedActualLevel, 151 | 'idealLevel': _selectedIdealLevel, 152 | }, 153 | ); 154 | } 155 | }), 156 | ), 157 | ], 158 | ), 159 | ), 160 | ); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/widgets/action_panel.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ActionPanel extends StatelessWidget { 4 | final String title; 5 | final Function onPressed; 6 | final Color panelColor; 7 | final bool enableDivider; 8 | 9 | ActionPanel({ 10 | @required this.title, 11 | @required this.onPressed, 12 | this.panelColor = Colors.white, 13 | this.enableDivider = true, 14 | }); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final theme = Theme.of(context); 19 | 20 | return Container( 21 | padding: const EdgeInsets.symmetric( 22 | vertical: 11.0, 23 | horizontal: 20.0, 24 | ), 25 | decoration: BoxDecoration( 26 | color: panelColor, 27 | boxShadow: enableDivider 28 | ? [ 29 | BoxShadow( 30 | blurRadius: 3.0, 31 | offset: Offset(0.0, -4.0), 32 | color: Colors.grey.shade100, 33 | ), 34 | ] 35 | : null, 36 | ), 37 | width: double.infinity, 38 | child: ButtonTheme( 39 | height: 45, 40 | child: FlatButton( 41 | onPressed: onPressed, 42 | shape: RoundedRectangleBorder( 43 | borderRadius: BorderRadius.circular(5.5), 44 | ), 45 | color: theme.accentColor, 46 | textColor: Colors.white, 47 | child: Text( 48 | title, 49 | style: const TextStyle( 50 | fontWeight: FontWeight.bold, 51 | letterSpacing: 1.2, 52 | ), 53 | ), 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/widgets/custom_safe_area.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomSafeArea extends StatelessWidget { 4 | final Widget child; 5 | final Color color; 6 | 7 | CustomSafeArea({ 8 | @required this.child, 9 | this.color = Colors.white, 10 | }); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | color: color, 16 | width: double.infinity, 17 | child: SafeArea( 18 | top: false, 19 | bottom: true, 20 | child: child, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/widgets/emoji_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class EmojiButton extends StatelessWidget { 4 | final String emoji; 5 | final String title; 6 | final String subtitle; 7 | final double height; 8 | final bool enableSubtitle; 9 | final Function onPressed; 10 | final Color backgroundColor; 11 | final Color outlineColor; 12 | 13 | EmojiButton({ 14 | @required this.emoji, 15 | @required this.title, 16 | this.subtitle = '', 17 | this.height = 55.0, 18 | this.enableSubtitle = false, 19 | @required this.onPressed, 20 | this.backgroundColor, 21 | this.outlineColor, 22 | }); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return DecoratedBox( 27 | decoration: BoxDecoration( 28 | borderRadius: BorderRadius.circular( 29 | 8.0, 30 | ), 31 | color: backgroundColor, 32 | ), 33 | child: ButtonTheme( 34 | height: height, 35 | child: OutlineButton( 36 | shape: RoundedRectangleBorder( 37 | borderRadius: BorderRadius.circular( 38 | 8.0, 39 | ), 40 | ), 41 | borderSide: BorderSide( 42 | color: outlineColor == null ? Colors.grey.shade100 : outlineColor, 43 | ), 44 | highlightColor: Colors.grey.shade100, 45 | highlightedBorderColor: Colors.grey.shade100, 46 | child: Row( 47 | children: [ 48 | Text( 49 | emoji, 50 | style: const TextStyle( 51 | fontSize: 18.0, 52 | ), 53 | ), 54 | SizedBox( 55 | width: 20.0, 56 | ), 57 | enableSubtitle 58 | ? Column( 59 | crossAxisAlignment: CrossAxisAlignment.start, 60 | children: [ 61 | Text( 62 | title, 63 | style: const TextStyle( 64 | fontSize: 16.0, 65 | ), 66 | ), 67 | SizedBox( 68 | height: 2, 69 | ), 70 | Text( 71 | subtitle, 72 | style: const TextStyle( 73 | fontSize: 12.0, 74 | ), 75 | ) 76 | ], 77 | ) 78 | : Text( 79 | title, 80 | style: const TextStyle( 81 | fontSize: 16.0, 82 | ), 83 | ), 84 | ], 85 | ), 86 | onPressed: onPressed, 87 | ), 88 | ), 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/widgets/knob_slider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class KnobSlider extends StatefulWidget { 4 | final int min; 5 | final int max; 6 | final int initialValue; 7 | final bool Function(int) onChanged; 8 | 9 | KnobSlider({ 10 | @required this.min, 11 | @required this.max, 12 | this.initialValue = 0, 13 | @required this.onChanged, 14 | }); 15 | 16 | @override 17 | _KnobSliderState createState() => _KnobSliderState(); 18 | } 19 | 20 | class _KnobSliderState extends State { 21 | var _isInit = true; 22 | double _sliderValue = 0.0; 23 | 24 | @override 25 | void didChangeDependencies() { 26 | super.didChangeDependencies(); 27 | if (_isInit) { 28 | _sliderValue = widget.initialValue.toDouble(); 29 | _isInit = false; 30 | } 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | final theme = Theme.of(context); 36 | 37 | return Row( 38 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 39 | children: [ 40 | Text( 41 | widget.min.toInt().toString(), 42 | style: TextStyle( 43 | fontWeight: FontWeight.bold, 44 | fontSize: 18.0, 45 | letterSpacing: 1.2, 46 | ), 47 | ), 48 | Expanded( 49 | child: SliderTheme( 50 | data: SliderTheme.of(context).copyWith( 51 | activeTrackColor: Colors.grey.shade200, 52 | inactiveTrackColor: Colors.grey.shade100, 53 | trackHeight: 9.0, 54 | thumbColor: theme.accentColor, 55 | thumbShape: const RoundSliderThumbShape( 56 | enabledThumbRadius: 15.0, 57 | disabledThumbRadius: 15.0, 58 | ), 59 | overlayColor: theme.accentColor.withAlpha(32), 60 | inactiveTickMarkColor: Colors.grey.shade200, 61 | valueIndicatorColor: theme.accentColor.withOpacity(0.95), 62 | ), 63 | child: Slider( 64 | min: widget.min.toDouble(), 65 | max: widget.max.toDouble(), 66 | value: _sliderValue, 67 | divisions: widget.max, 68 | label: _sliderValue.floor().toString(), 69 | onChanged: (value) { 70 | if (widget.onChanged(value.toInt())) { 71 | setState(() { 72 | _sliderValue = value; 73 | }); 74 | } 75 | }, 76 | ), 77 | ), 78 | ), 79 | Text( 80 | widget.max.toInt().toString(), 81 | style: TextStyle( 82 | fontWeight: FontWeight.bold, 83 | fontSize: 18.0, 84 | letterSpacing: 1.2, 85 | ), 86 | ), 87 | ], 88 | ); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/widgets/loading_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LoadingScreen extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Container( 7 | width: double.infinity, 8 | child: Center( 9 | child: CircularProgressIndicator( 10 | valueColor: AlwaysStoppedAnimation(Colors.grey.shade400), 11 | ), 12 | ), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/widgets/slider_question.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import './knob_slider.dart'; 4 | 5 | class SliderQuestion extends StatelessWidget { 6 | final String question; 7 | final int min; 8 | final int max; 9 | final int initialValue; 10 | final bool Function(int) onChanged; 11 | 12 | SliderQuestion({ 13 | @required this.question, 14 | @required this.min, 15 | @required this.max, 16 | @required this.initialValue, 17 | @required this.onChanged, 18 | }); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Column( 23 | mainAxisSize: MainAxisSize.min, 24 | crossAxisAlignment: CrossAxisAlignment.start, 25 | children: [ 26 | Flexible( 27 | child: Text( 28 | question, 29 | style: const TextStyle( 30 | fontWeight: FontWeight.bold, 31 | fontSize: 20.0, 32 | letterSpacing: 1.2, 33 | ), 34 | ), 35 | ), 36 | KnobSlider( 37 | min: min, 38 | initialValue: initialValue, 39 | max: max, 40 | onChanged: onChanged, 41 | ), 42 | ], 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | archive: 5 | dependency: transitive 6 | description: 7 | name: archive 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.0.11" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.5.2" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.4.0" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.5" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.1.3" 60 | cupertino_icons: 61 | dependency: "direct main" 62 | description: 63 | name: cupertino_icons 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.1.3" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_launcher_icons: 73 | dependency: "direct dev" 74 | description: 75 | name: flutter_launcher_icons 76 | url: "https://pub.dartlang.org" 77 | source: hosted 78 | version: "0.7.4" 79 | flutter_local_notifications: 80 | dependency: "direct main" 81 | description: 82 | name: flutter_local_notifications 83 | url: "https://pub.dartlang.org" 84 | source: hosted 85 | version: "1.2.2" 86 | flutter_local_notifications_platform_interface: 87 | dependency: transitive 88 | description: 89 | name: flutter_local_notifications_platform_interface 90 | url: "https://pub.dartlang.org" 91 | source: hosted 92 | version: "1.0.1" 93 | flutter_svg: 94 | dependency: "direct main" 95 | description: 96 | name: flutter_svg 97 | url: "https://pub.dartlang.org" 98 | source: hosted 99 | version: "0.17.3+1" 100 | flutter_test: 101 | dependency: "direct dev" 102 | description: flutter 103 | source: sdk 104 | version: "0.0.0" 105 | google_fonts: 106 | dependency: "direct main" 107 | description: 108 | name: google_fonts 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "0.3.9" 112 | http: 113 | dependency: transitive 114 | description: 115 | name: http 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "0.12.0+4" 119 | http_parser: 120 | dependency: transitive 121 | description: 122 | name: http_parser 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "3.1.3" 126 | image: 127 | dependency: transitive 128 | description: 129 | name: image 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "2.1.4" 133 | intl: 134 | dependency: "direct main" 135 | description: 136 | name: intl 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "0.16.1" 140 | matcher: 141 | dependency: transitive 142 | description: 143 | name: matcher 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "0.12.6" 147 | meta: 148 | dependency: transitive 149 | description: 150 | name: meta 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "1.1.8" 154 | nested: 155 | dependency: transitive 156 | description: 157 | name: nested 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.0.4" 161 | path: 162 | dependency: transitive 163 | description: 164 | name: path 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "1.6.4" 168 | path_drawing: 169 | dependency: transitive 170 | description: 171 | name: path_drawing 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "0.4.1" 175 | path_parsing: 176 | dependency: transitive 177 | description: 178 | name: path_parsing 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "0.1.4" 182 | path_provider: 183 | dependency: transitive 184 | description: 185 | name: path_provider 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "1.6.5" 189 | path_provider_macos: 190 | dependency: transitive 191 | description: 192 | name: path_provider_macos 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "0.0.4" 196 | path_provider_platform_interface: 197 | dependency: transitive 198 | description: 199 | name: path_provider_platform_interface 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "1.0.1" 203 | pedantic: 204 | dependency: transitive 205 | description: 206 | name: pedantic 207 | url: "https://pub.dartlang.org" 208 | source: hosted 209 | version: "1.9.0" 210 | petitparser: 211 | dependency: transitive 212 | description: 213 | name: petitparser 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "2.4.0" 217 | platform: 218 | dependency: transitive 219 | description: 220 | name: platform 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "2.2.1" 224 | plugin_platform_interface: 225 | dependency: transitive 226 | description: 227 | name: plugin_platform_interface 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "1.0.2" 231 | provider: 232 | dependency: "direct main" 233 | description: 234 | name: provider 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "4.0.4" 238 | quiver: 239 | dependency: transitive 240 | description: 241 | name: quiver 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "2.0.5" 245 | sky_engine: 246 | dependency: transitive 247 | description: flutter 248 | source: sdk 249 | version: "0.0.99" 250 | source_span: 251 | dependency: transitive 252 | description: 253 | name: source_span 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "1.5.5" 257 | sqflite: 258 | dependency: "direct main" 259 | description: 260 | name: sqflite 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "1.3.0" 264 | sqflite_common: 265 | dependency: transitive 266 | description: 267 | name: sqflite_common 268 | url: "https://pub.dartlang.org" 269 | source: hosted 270 | version: "1.0.0+1" 271 | stack_trace: 272 | dependency: transitive 273 | description: 274 | name: stack_trace 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "1.9.3" 278 | stream_channel: 279 | dependency: transitive 280 | description: 281 | name: stream_channel 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "2.0.0" 285 | string_scanner: 286 | dependency: transitive 287 | description: 288 | name: string_scanner 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.0.5" 292 | synchronized: 293 | dependency: transitive 294 | description: 295 | name: synchronized 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "2.2.0" 299 | term_glyph: 300 | dependency: transitive 301 | description: 302 | name: term_glyph 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "1.1.0" 306 | test_api: 307 | dependency: transitive 308 | description: 309 | name: test_api 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "0.2.15" 313 | typed_data: 314 | dependency: transitive 315 | description: 316 | name: typed_data 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "1.1.6" 320 | vector_math: 321 | dependency: transitive 322 | description: 323 | name: vector_math 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "2.0.8" 327 | xml: 328 | dependency: transitive 329 | description: 330 | name: xml 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "3.5.0" 334 | yaml: 335 | dependency: transitive 336 | description: 337 | name: yaml 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "2.2.0" 341 | sdks: 342 | dart: ">=2.7.0 <3.0.0" 343 | flutter: ">=1.12.13+hotfix.5 <2.0.0" 344 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: Helm 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.2.2 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | flutter_svg: ^0.17.3+1 23 | google_fonts: ^0.3.9 24 | intl: ^0.16.1 25 | provider: ^4.0.4 26 | sqflite: ^1.3.0 27 | flutter_local_notifications: ^1.2.2 28 | 29 | # The following adds the Cupertino Icons font to your application. 30 | # Use with the CupertinoIcons class for iOS style icons. 31 | cupertino_icons: ^0.1.3 32 | 33 | dev_dependencies: 34 | flutter_test: 35 | sdk: flutter 36 | flutter_launcher_icons: ^0.7.4 37 | 38 | flutter_icons: 39 | android: "launcher_icon" 40 | ios: true 41 | image_path: "assets/images/icon.png" 42 | 43 | # For information on the generic Dart part of this file, see the 44 | # following page: https://dart.dev/tools/pub/pubspec 45 | 46 | # The following section is specific to Flutter. 47 | flutter: 48 | 49 | # The following line ensures that the Material Icons font is 50 | # included with your application, so that you can use the icons in 51 | # the material Icons class. 52 | uses-material-design: true 53 | 54 | # To add assets to your application, add an assets section, like this: 55 | assets: 56 | - assets/images/yoga.svg 57 | - assets/images/drive.svg 58 | # - images/a_dot_burr.jpeg 59 | # - images/a_dot_ham.jpeg 60 | 61 | # An image asset can refer to one or more resolution-specific "variants", see 62 | # https://flutter.dev/assets-and-images/#resolution-aware. 63 | 64 | # For details regarding adding assets from package dependencies, see 65 | # https://flutter.dev/assets-and-images/#from-packages 66 | 67 | # To add custom fonts to your application, add a fonts section here, 68 | # in this "flutter" section. Each entry in this list should have a 69 | # "family" key with the font family name, and a "fonts" key with a 70 | # list giving the asset and other descriptors for the font. For 71 | # example: 72 | # fonts: 73 | # - family: Schyler 74 | # fonts: 75 | # - asset: fonts/Schyler-Regular.ttf 76 | # - asset: fonts/Schyler-Italic.ttf 77 | # style: italic 78 | # - family: Trajan Pro 79 | # fonts: 80 | # - asset: fonts/TrajanPro.ttf 81 | # - asset: fonts/TrajanPro_Bold.ttf 82 | # weight: 700 83 | # 84 | # For details regarding fonts from package dependencies, 85 | # see https://flutter.dev/custom-fonts/#from-packages 86 | --------------------------------------------------------------------------------