diff --git a/.github/workflows/all_packages.yml b/.github/workflows/all_packages.yml
index 9716b42e2..ab7f22858 100644
--- a/.github/workflows/all_packages.yml
+++ b/.github/workflows/all_packages.yml
@@ -26,7 +26,7 @@ jobs:
- name: Init workspace
uses: ./.github/workflows/init-workspace
- name: Run analyze
- run: melos run analyze
+ run: melos analyze
format:
runs-on: ubuntu-latest
@@ -38,7 +38,7 @@ jobs:
- name: Init workspace
uses: ./.github/workflows/init-workspace
- name: Run format
- run: melos run format:ci
+ run: melos format --set-exit-if-changed
test:
runs-on: ubuntu-latest
diff --git a/.github/workflows/init-workspace/action.yml b/.github/workflows/init-workspace/action.yml
index 5b4d32b82..565c0c76e 100644
--- a/.github/workflows/init-workspace/action.yml
+++ b/.github/workflows/init-workspace/action.yml
@@ -10,7 +10,7 @@ runs:
with:
channel: stable
# Increase this version manually when we add support for a new Flutter release
- flutter-version: '3.19.6'
+ flutter-version: '3.22.x'
- name: Install melos
run: dart pub global activate melos
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23f3fc9b7..1dcb3ad06 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,129 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## 2024-06-12
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`studyu_app` - `v2.7.6-dev.2`](#studyu_app---v276-dev2)
+ - [`studyu_core` - `v4.4.3-dev.1`](#studyu_core---v443-dev1)
+ - [`studyu_designer_v2` - `v1.8.1-dev.2`](#studyu_designer_v2---v181-dev2)
+ - [`studyu_flutter_common` - `v1.8.4-dev.1`](#studyu_flutter_common---v184-dev1)
+
+---
+
+#### `studyu_app` - `v2.7.6-dev.2`
+
+ - **FIX**: formatting issues.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+ - **FIX**: upgrade deps.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+ - **FIX**(app): update android deployment.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+
+#### `studyu_core` - `v4.4.3-dev.1`
+
+ - **PERF**: improve dashboard study fetching.
+ - **FIX**: formatting issues.
+ - **FIX**: upgrade deps.
+
+#### `studyu_designer_v2` - `v1.8.1-dev.2`
+
+ - **PERF**: added comments to the getUserStudies function.
+ - **PERF**: improve dashboard study fetching.
+ - **FIX**: add emojis again.
+ - **FIX**: integration test sign out.
+ - **FIX**: check if canPop.
+ - **FIX**: upgrade deps.
+ - **FIX**: add compatibility for emoji font with flutter >= 3.22.
+ - **FIX**: Flutter 3.22 arg error.
+
+#### `studyu_flutter_common` - `v1.8.4-dev.1`
+
+ - **FIX**: formatting issues.
+
+
+## 2024-06-09
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`studyu_app` - `v2.7.6-dev.1`](#studyu_app---v276-dev1)
+ - [`studyu_designer_v2` - `v1.8.1-dev.1`](#studyu_designer_v2---v181-dev1)
+
+---
+
+#### `studyu_app` - `v2.7.6-dev.1`
+
+ - **FIX**(app): update android deployment.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+
+#### `studyu_designer_v2` - `v1.8.1-dev.1`
+
+ - **FIX**: add emojis again.
+
+
+## 2024-06-07
+
+### Changes
+
+---
+
+Packages with breaking changes:
+
+ - There are no breaking changes in this release.
+
+Packages with other changes:
+
+ - [`studyu_app` - `v2.7.6-dev.0`](#studyu_app---v276-dev0)
+ - [`studyu_core` - `v4.4.3-dev.0`](#studyu_core---v443-dev0)
+ - [`studyu_designer_v2` - `v1.8.1-dev.0`](#studyu_designer_v2---v181-dev0)
+ - [`studyu_flutter_common` - `v1.8.4-dev.0`](#studyu_flutter_common---v184-dev0)
+
+---
+
+#### `studyu_app` - `v2.7.6-dev.0`
+
+ - **FIX**: upgrade deps.
+ - **FIX**: UI overflows.
+
+#### `studyu_core` - `v4.4.3-dev.0`
+
+ - **FIX**: upgrade deps.
+
+#### `studyu_designer_v2` - `v1.8.1-dev.0`
+
+ - **FIX**: integration test sign out.
+ - **FIX**: check if canPop.
+ - **FIX**: upgrade deps.
+ - **FIX**: add compatibility for emoji font with flutter >= 3.22.
+ - **FIX**: Flutter 3.22 arg error.
+
+#### `studyu_flutter_common` - `v1.8.4-dev.0`
+
+ - **FIX**: upgrade deps.
+ - **FIX**: put supabase cli default anon key in env.local.example.
+
+
## 2024-05-13
### Changes
diff --git a/app/.metadata b/app/.metadata
index eea2802f1..6eb54a17b 100644
--- a/app/.metadata
+++ b/app/.metadata
@@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
- revision: "67457e669f79e9f8d13d7a68fe09775fefbb79f4"
+ revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49"
channel: "stable"
project_type: app
@@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
- create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: android
- create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: ios
- create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: linux
- create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: macos
- create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: web
- create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
- platform: windows
- create_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
- base_revision: 67457e669f79e9f8d13d7a68fe09775fefbb79f4
+ create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
+ base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49
# User provided section
diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md
index cc511277d..7530e8eea 100644
--- a/app/CHANGELOG.md
+++ b/app/CHANGELOG.md
@@ -1,3 +1,26 @@
+## 2.7.6-dev.2
+
+ - **FIX**: formatting issues.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+ - **FIX**: upgrade deps.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+ - **FIX**(app): update android deployment.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+
+## 2.7.6-dev.1
+
+ - **FIX**(app): update android deployment.
+ - **FIX**(app): update ios deployment.
+ - **FIX**(app): update android deployment.
+
+## 2.7.6-dev.0
+
+ - **FIX**: upgrade deps.
+ - **FIX**: UI overflows.
+
## 2.7.5
- **FIX**: update podfile.
diff --git a/app/analysis_options.yaml b/app/analysis_options.yaml
index ca8edeaa3..f689b7b48 100644
--- a/app/analysis_options.yaml
+++ b/app/analysis_options.yaml
@@ -1 +1,7 @@
-include: ../flutter_analysis_options.yaml
+include: ../analysis_options.yaml
+
+analyzer:
+ errors:
+ avoid_classes_with_only_static_members: ignore
+ plugins:
+ - custom_lint
diff --git a/app/android/Gemfile.lock b/app/android/Gemfile.lock
index cb09ef947..daec0568f 100644
--- a/app/android/Gemfile.lock
+++ b/app/android/Gemfile.lock
@@ -10,17 +10,17 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
- aws-partitions (1.914.0)
- aws-sdk-core (3.192.0)
+ aws-partitions (1.943.0)
+ aws-sdk-core (3.197.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.79.0)
- aws-sdk-core (~> 3, >= 3.191.0)
+ aws-sdk-kms (1.83.0)
+ aws-sdk-core (~> 3, >= 3.197.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.147.0)
- aws-sdk-core (~> 3, >= 3.192.0)
+ aws-sdk-s3 (1.152.1)
+ aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
@@ -147,7 +147,7 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
- http-cookie (1.0.5)
+ http-cookie (1.0.6)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
@@ -157,7 +157,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
multi_json (1.15.0)
- multipart-post (2.4.0)
+ multipart-post (2.4.1)
nanaimo (0.3.0)
naturally (2.2.1)
nkf (0.2.0)
@@ -171,7 +171,8 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
- rexml (3.2.6)
+ rexml (3.2.9)
+ strscan
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@@ -184,6 +185,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
+ strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle
index 0d97d49a1..dbbbe5092 100644
--- a/app/android/app/build.gradle
+++ b/app/android/app/build.gradle
@@ -1,25 +1,26 @@
plugins {
id "com.android.application"
id "kotlin-android"
+ // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties()
-def localPropertiesFile = rootProject.file('local.properties')
+def localPropertiesFile = rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
- localPropertiesFile.withReader('UTF-8') { reader ->
+ localPropertiesFile.withReader("UTF-8") { reader ->
localProperties.load(reader)
}
}
-def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
if (flutterVersionCode == null) {
- flutterVersionCode = '1'
+ flutterVersionCode = "1"
}
-def flutterVersionName = localProperties.getProperty('flutter.versionName')
+def flutterVersionName = localProperties.getProperty("flutter.versionName")
if (flutterVersionName == null) {
- flutterVersionName = '1.0'
+ flutterVersionName = "1.0"
}
def keystoreProperties = new Properties()
@@ -29,45 +30,39 @@ if (keystorePropertiesFile.exists()) {
}
android {
- namespace "health.studyu.app"
+ namespace = "health.studyu.app"
// Start flutter_local_notifications
- compileSdkVersion Math.max(flutter.compileSdkVersion, 34)
+ compileSdkVersion = Math.max(flutter.compileSdkVersion, 34)
// End flutter_local_notifications
-
- ndkVersion flutter.ndkVersion
+ // temp fix for record_audio package instead of "flutter.ndkVersion"
+ ndkVersion = "26.1.10909125"
// Start flutter_local_notifications
defaultConfig {
- multiDexEnabled true
+ multiDexEnabled = true
}
// End flutter_local_notifications
compileOptions {
// Start flutter_local_notifications
// Flag to enable support for the new language APIs
- coreLibraryDesugaringEnabled true
+ coreLibraryDesugaringEnabled = true
// End flutter_local_notifications
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
}
-
kotlinOptions {
- jvmTarget = '1.8'
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
+ jvmTarget = '17'
}
defaultConfig {
- applicationId "health.studyu.app"
+ applicationId = "health.studyu.app"
// You can update the following values to match your application needs.
- // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration
- // camera_android requires at least sdk 21
- minSdkVersion Math.max(flutter.minSdkVersion, 21)
- targetSdkVersion flutter.targetSdkVersion
- versionCode flutterVersionCode.toInteger()
- versionName flutterVersionName
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+ minSdk = Math.max(flutter.minSdkVersion, 23)
+ targetSdk = flutter.targetSdkVersion
+ versionCode = flutterVersionCode.toInteger()
+ versionName = flutterVersionName
}
signingConfigs {
@@ -80,13 +75,13 @@ android {
}
buildTypes {
release {
- signingConfig keystorePropertiesFile.exists() ? signingConfigs.release : null
+ signingConfig = keystorePropertiesFile.exists() ? signingConfigs.release : null
}
}
}
flutter {
- source '../..'
+ source = "../.."
}
dependencies {
diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml
index 9a2bf234f..f195e55a9 100644
--- a/app/android/app/src/main/AndroidManifest.xml
+++ b/app/android/app/src/main/AndroidManifest.xml
@@ -34,6 +34,7 @@
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
+ android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
@@ -68,4 +69,15 @@
android:name="flutterEmbedding"
android:value="2" />
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/kotlin/health/studyu/app/studyu_app/MainActivity.kt b/app/android/app/src/main/kotlin/health/studyu/app/MainActivity.kt
similarity index 65%
rename from app/android/app/src/main/kotlin/health/studyu/app/studyu_app/MainActivity.kt
rename to app/android/app/src/main/kotlin/health/studyu/app/MainActivity.kt
index fdeb24d36..2e44fe3cb 100644
--- a/app/android/app/src/main/kotlin/health/studyu/app/studyu_app/MainActivity.kt
+++ b/app/android/app/src/main/kotlin/health/studyu/app/MainActivity.kt
@@ -2,5 +2,4 @@ package health.studyu.app
import io.flutter.embedding.android.FlutterActivity
-class MainActivity: FlutterActivity() {
-}
+class MainActivity: FlutterActivity()
diff --git a/app/android/build.gradle b/app/android/build.gradle
index e83fb5dac..d2ffbffa4 100644
--- a/app/android/build.gradle
+++ b/app/android/build.gradle
@@ -1,15 +1,3 @@
-buildscript {
- ext.kotlin_version = '1.7.10'
- repositories {
- google()
- mavenCentral()
- }
-
- dependencies {
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- }
-}
-
allprojects {
repositories {
google()
@@ -17,12 +5,12 @@ allprojects {
}
}
-rootProject.buildDir = '../build'
+rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
- project.evaluationDependsOn(':app')
+ project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
diff --git a/app/android/gradle.properties b/app/android/gradle.properties
index 598d13fee..3b5b324f6 100644
--- a/app/android/gradle.properties
+++ b/app/android/gradle.properties
@@ -1,3 +1,3 @@
-org.gradle.jvmargs=-Xmx4G
+org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties
index 3c472b99c..5af0f539c 100644
--- a/app/android/gradle/wrapper/gradle-wrapper.properties
+++ b/app/android/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip
diff --git a/app/android/settings.gradle b/app/android/settings.gradle
index 7cd712855..2054f554f 100644
--- a/app/android/settings.gradle
+++ b/app/android/settings.gradle
@@ -5,25 +5,21 @@ pluginManagement {
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
- }
- settings.ext.flutterSdkPath = flutterSdkPath()
+ }()
- includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
+ includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
-
- plugins {
- id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
- }
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "7.3.0" apply false
+ id "com.android.application" version "8.4.0" apply false
+ id "org.jetbrains.kotlin.android" version "1.9.23" apply false
}
include ":app"
diff --git a/app/ios/Flutter/AppFrameworkInfo.plist b/app/ios/Flutter/AppFrameworkInfo.plist
index 658d8c43c..7c5696400 100644
--- a/app/ios/Flutter/AppFrameworkInfo.plist
+++ b/app/ios/Flutter/AppFrameworkInfo.plist
@@ -2,25 +2,25 @@
- CFBundleDevelopmentRegion
- en
- CFBundleExecutable
- App
- CFBundleIdentifier
- io.flutter.flutter.app
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- App
- CFBundlePackageType
- FMWK
- CFBundleShortVersionString
- 1.0
- CFBundleSignature
- ????
- CFBundleVersion
- 1.0
- MinimumOSVersion
- 12.0
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 12.0
diff --git a/app/ios/Gemfile.lock b/app/ios/Gemfile.lock
index cb09ef947..daec0568f 100644
--- a/app/ios/Gemfile.lock
+++ b/app/ios/Gemfile.lock
@@ -10,17 +10,17 @@ GEM
artifactory (3.0.17)
atomos (0.1.3)
aws-eventstream (1.3.0)
- aws-partitions (1.914.0)
- aws-sdk-core (3.192.0)
+ aws-partitions (1.943.0)
+ aws-sdk-core (3.197.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.8)
jmespath (~> 1, >= 1.6.1)
- aws-sdk-kms (1.79.0)
- aws-sdk-core (~> 3, >= 3.191.0)
+ aws-sdk-kms (1.83.0)
+ aws-sdk-core (~> 3, >= 3.197.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.147.0)
- aws-sdk-core (~> 3, >= 3.192.0)
+ aws-sdk-s3 (1.152.1)
+ aws-sdk-core (~> 3, >= 3.197.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.8)
aws-sigv4 (1.8.0)
@@ -147,7 +147,7 @@ GEM
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
highline (2.0.3)
- http-cookie (1.0.5)
+ http-cookie (1.0.6)
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.6.2)
@@ -157,7 +157,7 @@ GEM
mini_magick (4.12.0)
mini_mime (1.1.5)
multi_json (1.15.0)
- multipart-post (2.4.0)
+ multipart-post (2.4.1)
nanaimo (0.3.0)
naturally (2.2.1)
nkf (0.2.0)
@@ -171,7 +171,8 @@ GEM
trailblazer-option (>= 0.1.1, < 0.2.0)
uber (< 0.2.0)
retriable (3.1.2)
- rexml (3.2.6)
+ rexml (3.2.9)
+ strscan
rouge (2.0.7)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
@@ -184,6 +185,7 @@ GEM
simctl (1.6.10)
CFPropertyList
naturally
+ strscan (3.1.0)
terminal-notifier (2.0.0)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock
index bf4adfc7d..209989e65 100644
--- a/app/ios/Podfile.lock
+++ b/app/ios/Podfile.lock
@@ -26,11 +26,11 @@ PODS:
- record_darwin (1.0.0):
- Flutter
- FlutterMacOS
- - Sentry/HybridSDK (8.25.0)
- - sentry_flutter (8.1.0):
+ - Sentry/HybridSDK (8.25.2)
+ - sentry_flutter (8.2.0):
- Flutter
- FlutterMacOS
- - Sentry/HybridSDK (= 8.25.0)
+ - Sentry/HybridSDK (= 8.25.2)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
@@ -126,15 +126,15 @@ SPEC CHECKSUMS:
flutter_timezone: ffb07bdad3c6276af8dada0f11978d8a1f8a20bb
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
- path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
+ path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
record_darwin: 1f6619f2abac4d1ca91d3eeab038c980d76f1517
- Sentry: cd86fc55628f5b7c572cabe66cc8f95a9d2f165a
- sentry_flutter: ca7760fc008dc3bc2981730dc0c1d2f892178370
- shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
+ Sentry: 51b056d96914a741f63eca774d118678b1eb05a1
+ sentry_flutter: e8397d13e297a5d4b6be8a752e33140b21c5cc97
+ shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
- url_launcher_ios: 6116280ddcfe98ab8820085d8d76ae7449447586
- video_player_avfoundation: 2b4384f3b157206b5e150a0083cdc0c905d260d3
+ url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
+ video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
webview_flutter_wkwebview: be0f0d33777f1bfd0c9fdcb594786704dbf65f36
diff --git a/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..f9b0d7c5e
--- /dev/null
+++ b/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/app/ios/Runner/AppDelegate.swift b/app/ios/Runner/AppDelegate.swift
index f0630aa0c..aa0522ca0 100644
--- a/app/ios/Runner/AppDelegate.swift
+++ b/app/ios/Runner/AppDelegate.swift
@@ -1,5 +1,5 @@
-import UIKit
import Flutter
+import UIKit
import flutter_local_notifications
@UIApplicationMain
diff --git a/app/ios/Runner/Info.plist b/app/ios/Runner/Info.plist
index 752966c2e..391233a29 100644
--- a/app/ios/Runner/Info.plist
+++ b/app/ios/Runner/Info.plist
@@ -2,8 +2,6 @@
- CADisableMinimumFrameDurationOnPhone
-
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
@@ -71,9 +69,11 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- UIViewControllerBasedStatusBarAppearance
-
- NSCameraUsageDescription
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+ NSCameraUsageDescription
Need to access your camera to capture a photo for trial observations.
diff --git a/app/ios/Runner/Runner.entitlements b/app/ios/Runner/Runner.entitlements
index 28c29bf6d..951769cc4 100644
--- a/app/ios/Runner/Runner.entitlements
+++ b/app/ios/Runner/Runner.entitlements
@@ -1,4 +1,3 @@
-
diff --git a/app/lib/app.dart b/app/lib/app.dart
index 3c5d63835..293d87437 100644
--- a/app/lib/app.dart
+++ b/app/lib/app.dart
@@ -2,17 +2,21 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
+import 'package:studyu_app/main.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/theme.dart';
import 'package:studyu_app/util/app_analytics.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
-import 'main.dart';
-import 'models/app_state.dart';
-import 'routes.dart';
-import 'theme.dart';
-
class MyApp extends StatefulWidget {
- const MyApp(this.queryParameters, this.appConfig, {super.key, required this.initialRoute});
+ const MyApp(
+ this.queryParameters,
+ this.appConfig, {
+ super.key,
+ required this.initialRoute,
+ });
final Map queryParameters;
final AppConfig? appConfig;
final String initialRoute;
@@ -31,7 +35,9 @@ class _MyAppState extends State {
Widget build(BuildContext context) {
return MultiProvider(
providers: [
- ChangeNotifierProvider(create: (context) => AppLanguage(AppLocalizations.supportedLocales)),
+ ChangeNotifierProvider(
+ create: (context) => AppLanguage(AppLocalizations.supportedLocales),
+ ),
ChangeNotifierProvider(create: (context) => AppState()),
],
child: Consumer(
@@ -50,7 +56,8 @@ class _MyAppState extends State {
],
localeListResolutionCallback: (locales, supportedLocales) {
// print('device locales=$locales supported locales=$supportedLocales');
- final supportedLanguageCodes = supportedLocales.map((e) => e.languageCode);
+ final supportedLanguageCodes =
+ supportedLocales.map((e) => e.languageCode);
if (locales != null) {
for (final locale in locales) {
if (supportedLanguageCodes.contains(locale.languageCode)) {
diff --git a/app/lib/constants.dart b/app/lib/constants.dart
index 1d5f7292b..0b755bb22 100644
--- a/app/lib/constants.dart
+++ b/app/lib/constants.dart
@@ -1,2 +1,3 @@
-const String playStoreUrl = 'https://play.google.com/store/apps/details?id=health.studyu.app';
+const String playStoreUrl =
+ 'https://play.google.com/store/apps/details?id=health.studyu.app';
const String appstoreUrl = 'https://itunes.apple.com/app/id1571991198';
diff --git a/app/lib/main.dart b/app/lib/main.dart
index a061af6a9..9de88a085 100644
--- a/app/lib/main.dart
+++ b/app/lib/main.dart
@@ -7,6 +7,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
import 'package:package_info_plus/package_info_plus.dart';
+import 'package:studyu_app/app.dart';
import 'package:studyu_app/routes.dart';
import 'package:studyu_app/util/app_analytics.dart';
import 'package:studyu_core/core.dart';
@@ -14,8 +15,6 @@ import 'package:studyu_flutter_common/studyu_flutter_common.dart';
import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
-import 'app.dart';
-
@pragma('vm:entry-point')
void notificationTapBackground(NotificationResponse notificationResponse) {
// ignore: avoid_print
@@ -24,11 +23,14 @@ void notificationTapBackground(NotificationResponse notificationResponse) {
' payload: ${notificationResponse.payload}');
if (notificationResponse.input?.isNotEmpty ?? false) {
// ignore: avoid_print
- print('notification action tapped with input: ${notificationResponse.input}');
+ print(
+ 'notification action tapped with input: ${notificationResponse.input}',
+ );
}
}
-GlobalKey navigatorKey = GlobalKey(debugLabel: 'Main Navigator');
+GlobalKey navigatorKey =
+ GlobalKey(debugLabel: 'Main Navigator');
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
@@ -51,37 +53,47 @@ Future main() async {
}
await AppAnalytics.init();
- if (!kDebugMode && AppAnalytics.isUserEnabled) {
- AppAnalytics.start(appConfig, MyApp(queryParameters, appConfig, initialRoute: initialRoute));
+ if (!kDebugMode &&
+ AppAnalytics.isUserEnabled != null &&
+ AppAnalytics.isUserEnabled!) {
+ AppAnalytics.start(
+ appConfig,
+ MyApp(queryParameters, appConfig, initialRoute: initialRoute),
+ );
} else {
runApp(MyApp(queryParameters, appConfig, initialRoute: initialRoute));
}
- AppLifecycleListener(onResume: () async {
- try {
- final navigatorState = navigatorKey.currentState;
- if (navigatorState == null) return;
- String? currentRoute;
- navigatorState.popUntil((route) {
- currentRoute = route.settings.name;
- return true;
- });
- if (currentRoute == Routes.appOutdated) return;
- final appConfig = await AppConfig.getAppConfig();
- if (await isAppOutdated(appConfig)) {
- await navigatorState.pushNamedAndRemoveUntil(Routes.appOutdated, (route) => false);
+ AppLifecycleListener(
+ onResume: () async {
+ try {
+ final navigatorState = navigatorKey.currentState;
+ if (navigatorState == null) return;
+ String? currentRoute;
+ navigatorState.popUntil((route) {
+ currentRoute = route.settings.name;
+ return true;
+ });
+ if (currentRoute == Routes.appOutdated) return;
+ final appConfig = await AppConfig.getAppConfig();
+ if (await isAppOutdated(appConfig)) {
+ await navigatorState.pushNamedAndRemoveUntil(
+ Routes.appOutdated,
+ (route) => false,
+ );
+ }
+ } catch (error) {
+ // device could be offline
+ debugPrint('Error fetching app config: $error');
}
- } catch (error) {
- // device could be offline
- debugPrint('Error fetching app config: $error');
- }
- });
+ },
+ );
}
/// Checks major and minor version of the app against the minimum version required by the backend
/// Returns true if the app is outdated
Future isAppOutdated(AppConfig appConfig) async {
- PackageInfo packageInfo = await PackageInfo.fromPlatform();
+ final PackageInfo packageInfo = await PackageInfo.fromPlatform();
final appVersion = packageInfo.version;
final appVersionParts = appVersion.split('.');
final minVersionParts = appConfig.appMinVersion.split('.');
@@ -92,7 +104,8 @@ Future isAppOutdated(AppConfig appConfig) async {
final minVersionMajor = int.parse(minVersionParts[0]);
final minVersionMinor = int.parse(minVersionParts[1]);
- return appVersionMajor < minVersionMajor || (appVersionMajor == minVersionMajor && appVersionMinor < minVersionMinor);
+ return appVersionMajor < minVersionMajor ||
+ (appVersionMajor == minVersionMajor && appVersionMinor < minVersionMinor);
}
/// This is needed for flutter_local_notifications
diff --git a/app/lib/models/app_state.dart b/app/lib/models/app_state.dart
index 6a4096129..0070628bb 100644
--- a/app/lib/models/app_state.dart
+++ b/app/lib/models/app_state.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:studyu_app/util/app_analytics.dart';
-import 'package:studyu_app/util/notifications.dart';
import 'package:studyu_app/util/cache.dart';
+import 'package:studyu_app/util/notifications.dart';
import 'package:studyu_app/util/schedule_notifications.dart';
import 'package:studyu_core/core.dart';
diff --git a/app/lib/routes.dart b/app/lib/routes.dart
index 6af899e7f..a0b9e0969 100644
--- a/app/lib/routes.dart
+++ b/app/lib/routes.dart
@@ -1,22 +1,21 @@
// ignore_for_file: avoid_classes_with_only_static_members
import 'package:flutter/material.dart';
-
-import 'screens/app_onboarding/about.dart';
-import 'screens/app_onboarding/loading_screen.dart';
-import 'screens/app_onboarding/terms.dart';
-import 'screens/app_onboarding/welcome.dart';
-import 'screens/app_onboarding/app_outdated_screen.dart';
-import 'screens/study/dashboard/contact_tab/contact_screen.dart';
-import 'screens/study/dashboard/contact_tab/faq.dart';
-import 'screens/study/dashboard/dashboard.dart';
-import 'screens/study/dashboard/settings.dart';
-import 'screens/study/onboarding/consent.dart';
-import 'screens/study/onboarding/intervention_selection.dart';
-import 'screens/study/onboarding/journey_overview.dart';
-import 'screens/study/onboarding/kickoff.dart';
-import 'screens/study/onboarding/study_overview.dart';
-import 'screens/study/onboarding/study_selection.dart';
-import 'screens/study/report/report_history.dart';
+import 'package:studyu_app/screens/app_onboarding/about.dart';
+import 'package:studyu_app/screens/app_onboarding/app_outdated_screen.dart';
+import 'package:studyu_app/screens/app_onboarding/loading_screen.dart';
+import 'package:studyu_app/screens/app_onboarding/terms.dart';
+import 'package:studyu_app/screens/app_onboarding/welcome.dart';
+import 'package:studyu_app/screens/study/dashboard/contact_tab/contact_screen.dart';
+import 'package:studyu_app/screens/study/dashboard/contact_tab/faq.dart';
+import 'package:studyu_app/screens/study/dashboard/dashboard.dart';
+import 'package:studyu_app/screens/study/dashboard/settings.dart';
+import 'package:studyu_app/screens/study/onboarding/consent.dart';
+import 'package:studyu_app/screens/study/onboarding/intervention_selection.dart';
+import 'package:studyu_app/screens/study/onboarding/journey_overview.dart';
+import 'package:studyu_app/screens/study/onboarding/kickoff.dart';
+import 'package:studyu_app/screens/study/onboarding/study_overview.dart';
+import 'package:studyu_app/screens/study/onboarding/study_selection.dart';
+import 'package:studyu_app/screens/study/report/report_history.dart';
class Routes {
static const String loading = '/loading';
@@ -47,7 +46,9 @@ class Routes {
child: Center(
child: Padding(
padding: const EdgeInsets.all(16),
- child: Text('No route defined for ${settings.name}.\nThe developers should fix this 👩💻'),
+ child: Text(
+ 'No route defined for ${settings.name}.\nThe developers should fix this 👩💻',
+ ),
),
),
),
@@ -55,43 +56,97 @@ class Routes {
);
}
- static Route? generateRoute(RouteSettings settings, Map queryParameters) {
+ static Route? generateRoute(
+ RouteSettings settings,
+ Map queryParameters,
+ ) {
final uri = Uri.parse(settings.name!);
switch (uri.path) {
case loading:
- return MaterialPageRoute(builder: (_) => const LoadingScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const LoadingScreen(),
+ settings: settings,
+ );
case preview:
- return MaterialPageRoute(builder: (_) => LoadingScreen(queryParameters: queryParameters), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => LoadingScreen(queryParameters: queryParameters),
+ settings: settings,
+ );
case appOutdated:
- return MaterialPageRoute(builder: (_) => const AppOutdatedScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const AppOutdatedScreen(),
+ settings: settings,
+ );
case dashboard:
- return MaterialPageRoute(builder: (_) => const DashboardScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const DashboardScreen(),
+ settings: settings,
+ );
case welcome:
- return MaterialPageRoute(builder: (_) => const WelcomeScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const WelcomeScreen(),
+ settings: settings,
+ );
case about:
- return MaterialPageRoute(builder: (_) => const AboutScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const AboutScreen(),
+ settings: settings,
+ );
case terms:
- return MaterialPageRoute(builder: (_) => const TermsScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const TermsScreen(),
+ settings: settings,
+ );
case studySelection:
- return MaterialPageRoute(builder: (_) => const StudySelectionScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const StudySelectionScreen(),
+ settings: settings,
+ );
case studyOverview:
- return MaterialPageRoute(builder: (_) => const StudyOverviewScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const StudyOverviewScreen(),
+ settings: settings,
+ );
case interventionSelection:
- return MaterialPageRoute(builder: (_) => const InterventionSelectionScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const InterventionSelectionScreen(),
+ settings: settings,
+ );
case journey:
- return MaterialPageRoute(builder: (_) => const JourneyOverviewScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const JourneyOverviewScreen(),
+ settings: settings,
+ );
case consent:
- return MaterialPageRoute(builder: (_) => const ConsentScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const ConsentScreen(),
+ settings: settings,
+ );
case kickoff:
- return MaterialPageRoute(builder: (_) => const KickoffScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const KickoffScreen(),
+ settings: settings,
+ );
case contact:
- return MaterialPageRoute(builder: (_) => const ContactScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const ContactScreen(),
+ settings: settings,
+ );
case faq:
- return MaterialPageRoute(builder: (_) => const FAQ(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const FAQ(),
+ settings: settings,
+ );
case appSettings:
- return MaterialPageRoute(builder: (_) => const Settings(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const Settings(),
+ settings: settings,
+ );
case reportHistory:
- return MaterialPageRoute(builder: (_) => const ReportHistoryScreen(), settings: settings);
+ return MaterialPageRoute(
+ builder: (_) => const ReportHistoryScreen(),
+ settings: settings,
+ );
default:
//final potentialSessionString = Uri.decodeComponent(settings.name.replaceFirst('/', ''));
//return MaterialPageRoute(builder: (_) => LoadingScreen(sessionString: potentialSessionString));
diff --git a/app/lib/screens/app_onboarding/about.dart b/app/lib/screens/app_onboarding/about.dart
index 1c7bae717..3f8606448 100644
--- a/app/lib/screens/app_onboarding/about.dart
+++ b/app/lib/screens/app_onboarding/about.dart
@@ -30,10 +30,15 @@ class AboutScreen extends StatelessWidget {
child: Icon(MdiIcons.food, size: 80, color: Colors.black),
),
Expanded(
- child: Icon(MdiIcons.equal, size: 80, color: Colors.black),
+ child:
+ Icon(MdiIcons.equal, size: 80, color: Colors.black),
),
Expanded(
- child: Icon(MdiIcons.sleepOff, size: 80, color: Colors.black),
+ child: Icon(
+ MdiIcons.sleepOff,
+ size: 80,
+ color: Colors.black,
+ ),
),
],
),
@@ -69,7 +74,8 @@ class AboutScreen extends StatelessWidget {
Row(
children: [
Expanded(
- child: Icon(MdiIcons.help, size: 80, color: Colors.orange),
+ child:
+ Icon(MdiIcons.help, size: 80, color: Colors.orange),
),
],
),
@@ -105,7 +111,11 @@ class AboutScreen extends StatelessWidget {
Row(
children: [
Expanded(
- child: Icon(MdiIcons.accountQuestion, size: 80, color: Colors.blue),
+ child: Icon(
+ MdiIcons.accountQuestion,
+ size: 80,
+ color: Colors.blue,
+ ),
),
],
),
@@ -141,8 +151,12 @@ class AboutScreen extends StatelessWidget {
Row(
children: [
Expanded(
- child: Icon(MdiIcons.exclamationThick, size: 80, color: Colors.blue),
- )
+ child: Icon(
+ MdiIcons.exclamationThick,
+ size: 80,
+ color: Colors.blue,
+ ),
+ ),
],
),
const SizedBox(height: 50),
@@ -177,7 +191,11 @@ class AboutScreen extends StatelessWidget {
Row(
children: [
Expanded(
- child: Icon(MdiIcons.alphaNBoxOutline, size: 80, color: Colors.blue),
+ child: Icon(
+ MdiIcons.alphaNBoxOutline,
+ size: 80,
+ color: Colors.blue,
+ ),
),
const Expanded(
child: Text(
@@ -187,7 +205,11 @@ class AboutScreen extends StatelessWidget {
),
),
Expanded(
- child: Icon(MdiIcons.numeric1BoxOutline, size: 80, color: Colors.blue),
+ child: Icon(
+ MdiIcons.numeric1BoxOutline,
+ size: 80,
+ color: Colors.blue,
+ ),
),
],
),
@@ -223,7 +245,11 @@ class AboutScreen extends StatelessWidget {
Row(
children: [
Expanded(
- child: Icon(MdiIcons.notebookOutline, size: 80, color: Colors.blue),
+ child: Icon(
+ MdiIcons.notebookOutline,
+ size: 80,
+ color: Colors.blue,
+ ),
),
],
),
@@ -259,7 +285,11 @@ class AboutScreen extends StatelessWidget {
Row(
children: [
Expanded(
- child: Icon(MdiIcons.alignVerticalBottom, size: 80, color: Colors.blue),
+ child: Icon(
+ MdiIcons.alignVerticalBottom,
+ size: 80,
+ color: Colors.blue,
+ ),
),
],
),
@@ -295,7 +325,11 @@ class AboutScreen extends StatelessWidget {
Row(
children: [
Expanded(
- child: Icon(MdiIcons.progressCheck, size: 80, color: Colors.blue),
+ child: Icon(
+ MdiIcons.progressCheck,
+ size: 80,
+ color: Colors.blue,
+ ),
),
],
),
@@ -327,7 +361,10 @@ class AboutScreen extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
- const Image(image: AssetImage('assets/icon/logo.png'), height: 200),
+ const Image(
+ image: AssetImage('assets/icon/logo.png'),
+ height: 200,
+ ),
const SizedBox(height: 50),
Text(
AppLocalizations.of(context)!.description_part9,
@@ -339,7 +376,10 @@ class AboutScreen extends StatelessWidget {
OutlinedButton.icon(
icon: Icon(MdiIcons.rocket),
onPressed: () => Navigator.pushNamed(context, Routes.terms),
- label: Text(AppLocalizations.of(context)!.get_started, style: const TextStyle(fontSize: 20)),
+ label: Text(
+ AppLocalizations.of(context)!.get_started,
+ style: const TextStyle(fontSize: 20),
+ ),
),
],
),
diff --git a/app/lib/screens/app_onboarding/app_outdated_screen.dart b/app/lib/screens/app_onboarding/app_outdated_screen.dart
index 61e18288c..876ecf49e 100644
--- a/app/lib/screens/app_onboarding/app_outdated_screen.dart
+++ b/app/lib/screens/app_onboarding/app_outdated_screen.dart
@@ -29,23 +29,36 @@ class AppOutdatedScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
- const Image(image: AssetImage('assets/icon/logo.png'), height: 200),
+ const Image(
+ image: AssetImage('assets/icon/logo.png'),
+ height: 200,
+ ),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.all(20),
- child:
- Text(loc.app_outdated_message, textAlign: TextAlign.center, style: const TextStyle(fontSize: 20)),
+ child: Text(
+ loc.app_outdated_message,
+ textAlign: TextAlign.center,
+ style: const TextStyle(fontSize: 20),
+ ),
),
const Spacer(),
- storeUrl != null && storeIcon != null
- ? OutlinedButton.icon(
- icon: Icon(storeIcon),
- onPressed: () async {
- await launchUrl(Uri.parse(storeUrl!), mode: LaunchMode.externalNonBrowserApplication);
- },
- label: Text(loc.update_now, style: const TextStyle(fontSize: 20)),
- )
- : const SizedBox.shrink(),
+ if (storeUrl != null && storeIcon != null)
+ OutlinedButton.icon(
+ icon: Icon(storeIcon),
+ onPressed: () async {
+ await launchUrl(
+ Uri.parse(storeUrl!),
+ mode: LaunchMode.externalNonBrowserApplication,
+ );
+ },
+ label: Text(
+ loc.update_now,
+ style: const TextStyle(fontSize: 20),
+ ),
+ )
+ else
+ const SizedBox.shrink(),
const Spacer(),
],
),
diff --git a/app/lib/screens/app_onboarding/loading_screen.dart b/app/lib/screens/app_onboarding/loading_screen.dart
index a719cfefb..2b218d67f 100644
--- a/app/lib/screens/app_onboarding/loading_screen.dart
+++ b/app/lib/screens/app_onboarding/loading_screen.dart
@@ -3,15 +3,15 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
import 'package:studyu_app/screens/app_onboarding/iframe_helper.dart';
+import 'package:studyu_app/screens/app_onboarding/preview.dart';
import 'package:studyu_app/screens/study/onboarding/eligibility_screen.dart';
import 'package:studyu_app/screens/study/tasks/task_screen.dart';
import 'package:studyu_app/util/cache.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
-import 'package:studyu_app/models/app_state.dart';
-import 'package:studyu_app/routes.dart';
-import 'preview.dart';
class LoadingScreen extends StatefulWidget {
final String? sessionString;
@@ -71,7 +71,9 @@ class _LoadingScreenState extends State {
try {
subject = await _fetchRemoteSubject(selectedStudyObjectId);
} catch (exception) {
- StudyULogger.warning("Could not retrieve subject, maybe JWT is expired, try logging in: ${exception.toString()}");
+ StudyULogger.warning(
+ "Could not retrieve subject, maybe JWT is expired, try logging in: $exception",
+ );
try {
// Try signing in again. Needed if JWT is expired
if (await signInParticipant()) {
@@ -90,7 +92,7 @@ class _LoadingScreenState extends State {
// 4. Open the app but do not join a study
// 5. Restart the app. Either only this error shows up, worst case is
// app hangs and is unresponsive
- StudyULogger.fatal('Could not login and retrieve the study subject.'
+ StudyULogger.fatal('Could not login and retrieve the study subject. '
'One reason for this might be that the study subject is no '
'longer available and only resides in app backup');
throw Exception("Remote subject not found");
@@ -100,12 +102,16 @@ class _LoadingScreenState extends State {
return subject;
}
- _initPreview(AppState state) async {
+ Future _initPreview(AppState state) async {
if (state.isPreview) previewSubjectIdKey();
- if (widget.queryParameters == null || widget.queryParameters!.isEmpty) return;
+ if (widget.queryParameters == null || widget.queryParameters!.isEmpty) {
+ return;
+ }
- StudyULogger.info("Preview: Found query parameters ${widget.queryParameters}");
- var lang = context.watch();
+ StudyULogger.info(
+ "Preview: Found query parameters ${widget.queryParameters}",
+ );
+ final lang = context.watch();
final preview = Preview(
widget.queryParameters,
lang,
@@ -131,7 +137,10 @@ class _LoadingScreenState extends State {
if (preview.selectedRoute == '/eligibilityCheck') {
if (!mounted) return;
// if we remove the await, we can push multiple times. warning: do not run in while(true)
- await Navigator.push(context, EligibilityScreen.routeFor(study: preview.study));
+ await Navigator.push(
+ context,
+ EligibilityScreen.routeFor(study: preview.study),
+ );
// either do the same navigator push again or --> send a message back to designer and let it reload the whole page <--
iFrameHelper.postRouteFinished();
return;
@@ -145,7 +154,8 @@ class _LoadingScreenState extends State {
return;
}
- state.activeSubject = await preview.getStudySubject(state, createSubject: true);
+ state.activeSubject =
+ await preview.getStudySubject(state, createSubject: true);
// CONSENT
if (preview.selectedRoute == Routes.consent) {
@@ -188,13 +198,19 @@ class _LoadingScreenState extends State {
if (preview.selectedRoute == '/observation') {
print(state.selectedStudy!.observations.first.id);
final tasks = [
- ...state.selectedStudy!.observations.where((observation) => observation.id == preview.extra),
+ ...state.selectedStudy!.observations
+ .where((observation) => observation.id == preview.extra),
];
if (!mounted) return;
await Navigator.push(
- context,
- TaskScreen.routeFor(
- taskInstance: TaskInstance(tasks.first, tasks.first.schedule.completionPeriods.first.id)));
+ context,
+ TaskScreen.routeFor(
+ taskInstance: TaskInstance(
+ tasks.first,
+ tasks.first.schedule.completionPeriods.first.id,
+ ),
+ ),
+ );
iFrameHelper.postRouteFinished();
return;
}
diff --git a/app/lib/screens/app_onboarding/preview.dart b/app/lib/screens/app_onboarding/preview.dart
index 8ce71c21a..709c25fd6 100644
--- a/app/lib/screens/app_onboarding/preview.dart
+++ b/app/lib/screens/app_onboarding/preview.dart
@@ -50,7 +50,8 @@ class Preview {
}
if (containsQuery('data')) {
- final data = jsonDecode(queryParameters!['data']!) as Map;
+ final data =
+ jsonDecode(queryParameters!['data']!) as Map;
study = Study.fromJson(data);
} else {
study = await SupabaseQuery.getById(queryParameters!['studyid']!);
@@ -108,7 +109,8 @@ class Preview {
}
bool containsQuery(String key) {
- return queryParameters!.containsKey(key) && queryParameters![key]!.isNotEmpty;
+ return queryParameters!.containsKey(key) &&
+ queryParameters![key]!.isNotEmpty;
}
bool containsQueryPair(String key, String value) {
@@ -116,7 +118,10 @@ class Preview {
}
/// createSubject: If true, the method will return a new StudySubject if none can be found. Otherwise, null is returned
- Future getStudySubject(AppState state, {bool createSubject = false}) async {
+ Future getStudySubject(
+ AppState state, {
+ bool createSubject = false,
+ }) async {
if (selectedStudyObjectId != null) {
try {
if (selectedRoute == '/intervention') {
@@ -134,10 +139,13 @@ class Preview {
(foundSubject) {
// todo baseline
foundSubject.study.schedule.includeBaseline = false;
- return foundSubject.userId == Supabase.instance.client.auth.currentUser!.id &&
+ return foundSubject.userId ==
+ Supabase.instance.client.auth.currentUser!.id &&
foundSubject.studyId == study!.id &&
listEquals(
- foundSubject.selectedInterventions.map((i) => i.id).toList(),
+ foundSubject.selectedInterventions
+ .map((i) => i.id)
+ .toList(),
getInterventionIds(),
);
},
diff --git a/app/lib/screens/app_onboarding/terms.dart b/app/lib/screens/app_onboarding/terms.dart
index 11f299ffd..1cc933944 100644
--- a/app/lib/screens/app_onboarding/terms.dart
+++ b/app/lib/screens/app_onboarding/terms.dart
@@ -2,13 +2,12 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/widgets/bottom_onboarding_navigation.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
import 'package:url_launcher/url_launcher.dart';
-import '../../routes.dart';
-import '../../widgets/bottom_onboarding_navigation.dart';
-
class TermsScreen extends StatefulWidget {
const TermsScreen({super.key});
@@ -30,8 +29,10 @@ class _TermsScreenState extends State {
body: SafeArea(
child: Center(
child: RetryFutureBuilder(
- tryFunction: AppConfig.getAppConfig,
- successBuilder: (BuildContext context, AppConfig? appConfig) => legalSection(context, appConfig)),
+ tryFunction: AppConfig.getAppConfig,
+ successBuilder: (BuildContext context, AppConfig? appConfig) =>
+ legalSection(context, appConfig),
+ ),
),
),
bottomNavigationBar: BottomOnboardingNavigation(
@@ -63,7 +64,7 @@ class _TermsScreenState extends State {
onChange: (val) => setState(() => _acceptedTerms = val!),
isChecked: _acceptedTerms,
icon: Icon(MdiIcons.fileDocumentEdit),
- pdfUrl: appConfig!.appTerms[appLocale.languageCode.toString()],
+ pdfUrl: appConfig!.appTerms[appLocale.languageCode],
pdfUrlLabel: AppLocalizations.of(context)!.terms_read,
),
const SizedBox(height: 20),
@@ -74,14 +75,16 @@ class _TermsScreenState extends State {
onChange: (val) => setState(() => _acceptedPrivacy = val!),
isChecked: _acceptedPrivacy,
icon: Icon(MdiIcons.shieldLock),
- pdfUrl: appConfig.appPrivacy[appLocale.languageCode.toString()],
+ pdfUrl: appConfig.appPrivacy[appLocale.languageCode],
pdfUrlLabel: AppLocalizations.of(context)!.privacy_read,
),
const SizedBox(height: 30),
OutlinedButton.icon(
icon: Icon(MdiIcons.scaleBalance),
onPressed: () async {
- final uri = Uri.parse(appConfig.imprint[appLocale.languageCode.toString()]!);
+ final uri = Uri.parse(
+ appConfig.imprint[appLocale.languageCode]!,
+ );
if (await canLaunchUrl(uri)) {
launchUrl(uri, mode: LaunchMode.externalApplication);
}
@@ -122,12 +125,16 @@ class LegalSection extends StatelessWidget {
final theme = Theme.of(context);
return Column(
children: [
- Text(title!, style: theme.textTheme.headlineMedium!.copyWith(color: theme.primaryColor)),
+ Text(
+ title!,
+ style: theme.textTheme.headlineMedium!
+ .copyWith(color: theme.primaryColor),
+ ),
const SizedBox(height: 20),
Text(description!),
const SizedBox(height: 20),
OutlinedButton.icon(
- icon: icon!,
+ icon: icon,
onPressed: () async {
final uri = Uri.parse(pdfUrl!);
if (await canLaunchUrl(uri)) {
@@ -136,7 +143,11 @@ class LegalSection extends StatelessWidget {
},
label: Text(pdfUrlLabel!),
),
- CheckboxListTile(title: Text(acknowledgment!), value: isChecked, onChanged: onChange),
+ CheckboxListTile(
+ title: Text(acknowledgment!),
+ value: isChecked,
+ onChanged: onChange,
+ ),
],
);
}
diff --git a/app/lib/screens/app_onboarding/welcome.dart b/app/lib/screens/app_onboarding/welcome.dart
index 48b17429c..175f5c04a 100644
--- a/app/lib/screens/app_onboarding/welcome.dart
+++ b/app/lib/screens/app_onboarding/welcome.dart
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
-import '../../routes.dart';
+import 'package:studyu_app/routes.dart';
class WelcomeScreen extends StatelessWidget {
const WelcomeScreen({super.key});
@@ -16,32 +16,47 @@ class WelcomeScreen extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Spacer(),
- const Image(image: AssetImage('assets/icon/logo.png'), height: 200),
+ const Image(
+ image: AssetImage('assets/icon/logo.png'),
+ height: 200,
+ ),
const SizedBox(height: 20),
OutlinedButton.icon(
icon: const Icon(Icons.info),
onPressed: () => Navigator.pushNamed(context, Routes.about),
- label: Text(AppLocalizations.of(context)!.what_is_studyu, style: const TextStyle(fontSize: 20)),
+ label: Text(
+ AppLocalizations.of(context)!.what_is_studyu,
+ style: const TextStyle(fontSize: 20),
+ ),
),
const SizedBox(height: 20),
OutlinedButton.icon(
icon: Icon(MdiIcons.accountBox),
onPressed: () => Navigator.pushNamed(context, Routes.contact),
- label: Text(AppLocalizations.of(context)!.contact, style: const TextStyle(fontSize: 20)),
+ label: Text(
+ AppLocalizations.of(context)!.contact,
+ style: const TextStyle(fontSize: 20),
+ ),
),
const SizedBox(height: 20),
OutlinedButton.icon(
icon: Icon(MdiIcons.frequentlyAskedQuestions),
onPressed: () => Navigator.pushNamed(context, Routes.faq),
- label: Text(AppLocalizations.of(context)!.faq, style: const TextStyle(fontSize: 20)),
+ label: Text(
+ AppLocalizations.of(context)!.faq,
+ style: const TextStyle(fontSize: 20),
+ ),
),
const Spacer(),
OutlinedButton.icon(
icon: Icon(MdiIcons.rocket, size: 30),
onPressed: () => Navigator.pushNamed(context, Routes.terms),
- label: Text(AppLocalizations.of(context)!.get_started, style: const TextStyle(fontSize: 20)),
+ label: Text(
+ AppLocalizations.of(context)!.get_started,
+ style: const TextStyle(fontSize: 20),
+ ),
),
- const Spacer()
+ const Spacer(),
],
),
),
diff --git a/app/lib/screens/study/dashboard/contact_tab/contact_screen.dart b/app/lib/screens/study/dashboard/contact_tab/contact_screen.dart
index 0302741ba..a2d9da904 100644
--- a/app/lib/screens/study/dashboard/contact_tab/contact_screen.dart
+++ b/app/lib/screens/study/dashboard/contact_tab/contact_screen.dart
@@ -2,12 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
import 'package:url_launcher/url_launcher.dart';
-import '../../../../models/app_state.dart';
-
class ContactScreen extends StatefulWidget {
const ContactScreen({super.key});
@@ -42,7 +41,9 @@ class _ContactScreenState extends State {
),
RetryFutureBuilder(
tryFunction: AppConfig.getAppContact,
- successBuilder: (BuildContext context, Contact? appSupportContact) => ContactWidget(
+ successBuilder:
+ (BuildContext context, Contact? appSupportContact) =>
+ ContactWidget(
contact: appSupportContact,
title: AppLocalizations.of(context)!.app_support,
subtitle: AppLocalizations.of(context)!.app_support_text,
@@ -68,7 +69,13 @@ class ContactWidget extends StatelessWidget {
final String? subtitle;
final Color color;
- const ContactWidget({required this.contact, required this.title, required this.color, this.subtitle, super.key});
+ const ContactWidget({
+ required this.contact,
+ required this.title,
+ required this.color,
+ this.subtitle,
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -77,9 +84,16 @@ class ContactWidget extends StatelessWidget {
return Container();
}
- final titles = [Text(title, style: theme.textTheme.titleLarge!.copyWith(color: color))];
+ final titles = [
+ Text(title, style: theme.textTheme.titleLarge!.copyWith(color: color)),
+ ];
if (subtitle != null && subtitle!.isNotEmpty) {
- titles.add(Text(subtitle!, style: theme.textTheme.titleMedium!.copyWith(fontSize: 14)));
+ titles.add(
+ Text(
+ subtitle!,
+ style: theme.textTheme.titleMedium!.copyWith(fontSize: 14),
+ ),
+ );
}
return Column(
@@ -98,7 +112,9 @@ class ContactWidget extends StatelessWidget {
ContactItem(
itemName: AppLocalizations.of(context)!.irb,
itemValue: contact!.institutionalReviewBoard! +
- (contact?.institutionalReviewBoardNumber != null ? ': ${contact?.institutionalReviewBoardNumber}' : ''),
+ (contact?.institutionalReviewBoardNumber != null
+ ? ': ${contact?.institutionalReviewBoardNumber}'
+ : ''),
iconData: MdiIcons.clipboardCheck,
iconColor: color,
),
@@ -164,18 +180,16 @@ class ContactItem extends StatelessWidget {
Uri uri;
switch (type) {
case ContactItemType.website:
- if (!itemValue!.startsWith('http://') && !itemValue!.startsWith('https://')) {
+ if (!itemValue!.startsWith('http://') &&
+ !itemValue!.startsWith('https://')) {
uri = Uri.parse('http://$itemValue');
} else {
uri = Uri.parse(itemValue!);
}
- break;
case ContactItemType.email:
uri = Uri.parse('mailto:$itemValue');
- break;
case ContactItemType.phone:
uri = Uri.parse('tel:$itemValue');
- break;
default:
uri = Uri.parse(itemValue!);
}
@@ -195,7 +209,11 @@ class ContactItem extends StatelessWidget {
return ListTile(
title: Text(itemName),
subtitle: SelectableText(itemValue!),
- leading: Icon(iconData, color: iconColor ?? Theme.of(context).primaryColor, size: iconSize),
+ leading: Icon(
+ iconData,
+ color: iconColor ?? Theme.of(context).primaryColor,
+ size: iconSize,
+ ),
onTap: type != null ? launchContact : null,
);
}
diff --git a/app/lib/screens/study/dashboard/contact_tab/faq.dart b/app/lib/screens/study/dashboard/contact_tab/faq.dart
index 997cc04f3..97e1cfc5a 100644
--- a/app/lib/screens/study/dashboard/contact_tab/faq.dart
+++ b/app/lib/screens/study/dashboard/contact_tab/faq.dart
@@ -7,7 +7,8 @@ class FAQ extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO(Manisha): Transfer strings to translation files
- if (AppLocalizations.of(context)!.faq_full == 'Frequently Asked Questions') {
+ if (AppLocalizations.of(context)!.faq_full ==
+ 'Frequently Asked Questions') {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.faq_full),
@@ -68,7 +69,9 @@ final List data_en = [
Entry(
'How long will the study take to finish?',
[
- Entry('The duration of each study is mentioned during initial study selection.'),
+ Entry(
+ 'The duration of each study is mentioned during initial study selection.',
+ ),
],
),
Entry(
@@ -93,7 +96,9 @@ final List data_en = [
Entry(
'How can I Opt out from the current study?',
[
- Entry('You can do so by going to the Settings tab located on the Dashboard and clicking on "Opt-out" '),
+ Entry(
+ 'You can do so by going to the Settings tab located on the Dashboard and clicking on "Opt-out" ',
+ ),
],
),
],
@@ -120,7 +125,9 @@ final List data_en = [
Entry(
'How can I keep track of my activities?',
[
- Entry('You can get an overview of your daily tasks and health status in the "Reports History section"'),
+ Entry(
+ 'You can get an overview of your daily tasks and health status in the "Reports History section"',
+ ),
],
),
Entry(
@@ -159,7 +166,9 @@ final data_de = [
Entry(
'Welche persönlichen Daten sammelt die App?',
[
- Entry('Die App sammelt keine persönlichen Daten des Benutzers, muss jedoch auf Zeit und Ort zugreifen.'),
+ Entry(
+ 'Die App sammelt keine persönlichen Daten des Benutzers, muss jedoch auf Zeit und Ort zugreifen.',
+ ),
],
),
],
@@ -170,7 +179,9 @@ final data_de = [
Entry(
'Wie lange dauert es, bis die Studie abgeschlossen ist?',
[
- Entry('Die Dauer jeder Studie wird bei der ersten Studienauswahl angegeben.'),
+ Entry(
+ 'Die Dauer jeder Studie wird bei der ersten Studienauswahl angegeben.',
+ ),
],
),
Entry(
diff --git a/app/lib/screens/study/dashboard/dashboard.dart b/app/lib/screens/study/dashboard/dashboard.dart
index 90041fc96..108970a69 100644
--- a/app/lib/screens/study/dashboard/dashboard.dart
+++ b/app/lib/screens/study/dashboard/dashboard.dart
@@ -9,16 +9,15 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
import 'package:package_info_plus/package_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/screens/study/dashboard/task_overview_tab/task_overview.dart';
+import 'package:studyu_app/screens/study/report/report_details.dart';
import 'package:studyu_app/util/notifications.dart';
import 'package:studyu_app/util/schedule_notifications.dart';
import 'package:studyu_core/core.dart';
import 'package:url_launcher/url_launcher.dart';
-import '../../../models/app_state.dart';
-import '../../../routes.dart';
-import '../report/report_details.dart';
-import 'task_overview_tab/task_overview.dart';
-
class DashboardScreen extends StatefulWidget {
final String? error;
@@ -37,11 +36,14 @@ class OverflowMenuItem {
OverflowMenuItem(this.name, this.icon, {this.routeName, this.onTap});
}
-class _DashboardScreenState extends State with WidgetsBindingObserver {
+class _DashboardScreenState extends State
+ with WidgetsBindingObserver {
StudySubject? subject;
List? scheduleToday;
- get showNextDay => (kDebugMode || context.read().isPreview) && !subject!.completedStudy;
+ bool get showNextDay =>
+ (kDebugMode || context.read().isPreview) &&
+ !subject!.completedStudy;
@override
void initState() {
@@ -56,7 +58,6 @@ class _DashboardScreenState extends State with WidgetsBindingOb
setState(() {
scheduleToday = subject!.scheduleFor(DateTime.now());
});
- break;
case AppLifecycleState.inactive:
break;
case AppLifecycleState.paused:
@@ -76,7 +77,8 @@ class _DashboardScreenState extends State with WidgetsBindingOb
scheduleToday = subject!.scheduleFor(DateTime.now());
if (widget.error != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
- ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(widget.error!)));
+ ScaffoldMessenger.of(context)
+ .showSnackBar(SnackBar(content: Text(widget.error!)));
});
}
}
@@ -96,7 +98,11 @@ class _DashboardScreenState extends State with WidgetsBindingOb
Widget build(BuildContext context) {
if (subject == null) {
SchedulerBinding.instance.addPostFrameCallback((_) {
- Navigator.pushNamedAndRemoveUntil(context, Routes.loading, (_) => false);
+ Navigator.pushNamedAndRemoveUntil(
+ context,
+ Routes.loading,
+ (_) => false,
+ );
});
return const SizedBox.shrink();
}
@@ -117,14 +123,17 @@ class _DashboardScreenState extends State with WidgetsBindingOb
IconButton(
tooltip: 'Current report', // todo tr
icon: Icon(MdiIcons.chartBar),
- onPressed: () => Navigator.push(context, ReportDetailsScreen.routeFor(subject: subject!)),
+ onPressed: () => Navigator.push(
+ context,
+ ReportDetailsScreen.routeFor(subject: subject!),
+ ),
),
PopupMenuButton(
onSelected: (value) {
if (value.routeName != null) {
Navigator.pushNamed(context, value.routeName!);
- } else if (value.onTap != null) {
- value.onTap!();
+ } else {
+ value.onTap?.call();
}
},
itemBuilder: (context) {
@@ -139,7 +148,11 @@ class _DashboardScreenState extends State with WidgetsBindingOb
MdiIcons.frequentlyAskedQuestions,
routeName: Routes.faq,
),
- OverflowMenuItem(AppLocalizations.of(context)!.settings, Icons.settings, routeName: Routes.appSettings),
+ OverflowMenuItem(
+ AppLocalizations.of(context)!.settings,
+ Icons.settings,
+ routeName: Routes.appSettings,
+ ),
OverflowMenuItem(
AppLocalizations.of(context)!.what_is_studyu,
MdiIcons.helpCircleOutline,
@@ -150,53 +163,73 @@ class _DashboardScreenState extends State with WidgetsBindingOb
MdiIcons.informationOutline,
onTap: () async {
final iconAuthors = ['Kiranshastry'];
- final PackageInfo packageInfo = await PackageInfo.fromPlatform();
+ final PackageInfo packageInfo =
+ await PackageInfo.fromPlatform();
if (!context.mounted) return;
showAboutDialog(
context: context,
applicationIcon: GestureDetector(
onDoubleTap: () {
showDialog(
- context: context,
- builder: (_) => AlertDialog(
- title: const SelectableText('Notification Log'),
- content: Column(
- children: [
- ElevatedButton(
- onPressed: () {
- final Uri emailLaunchUri = Uri(
- scheme: 'mailto',
- path: subject!.study.contact.email,
- queryParameters: {
- 'subject': '[StudyU] Debug Information',
- 'body': StudyNotifications.scheduledNotificationsDebug,
- });
- launchUrl(emailLaunchUri);
- },
- child: const Text('Send via email'),
- ),
- FutureBuilder(
- future: receivePermission(),
- builder: (context, AsyncSnapshot snapshot) {
- if (snapshot.hasData) {
- String data = "ignoreBatteryOptimizations: ${snapshot.data.toString()}";
- StudyNotifications.scheduledNotificationsDebug =
- "${StudyNotifications.scheduledNotificationsDebug}\n\n$data\n";
- return Text(data);
- } else {
- return const CircularProgressIndicator();
- }
- }),
- SelectableText(StudyNotifications.scheduledNotificationsDebug!),
- ],
- ),
- scrollable: true,
- ));
+ context: context,
+ builder: (_) => AlertDialog(
+ title: const SelectableText(
+ 'Notification Log',
+ ),
+ content: Column(
+ children: [
+ ElevatedButton(
+ onPressed: () {
+ final Uri emailLaunchUri = Uri(
+ scheme: 'mailto',
+ path: subject!.study.contact.email,
+ queryParameters: {
+ 'subject':
+ '[StudyU] Debug Information',
+ 'body': StudyNotifications
+ .scheduledNotificationsDebug,
+ },
+ );
+ launchUrl(emailLaunchUri);
+ },
+ child: const Text('Send via email'),
+ ),
+ FutureBuilder(
+ future: receivePermission(),
+ builder: (
+ context,
+ AsyncSnapshot snapshot,
+ ) {
+ if (snapshot.hasData) {
+ final String data =
+ "ignoreBatteryOptimizations: ${snapshot.data}";
+ StudyNotifications
+ .scheduledNotificationsDebug =
+ "${StudyNotifications.scheduledNotificationsDebug}\n\n$data\n";
+ return Text(data);
+ } else {
+ return const CircularProgressIndicator();
+ }
+ },
+ ),
+ SelectableText(
+ StudyNotifications
+ .scheduledNotificationsDebug!,
+ ),
+ ],
+ ),
+ scrollable: true,
+ ),
+ );
testNotifications(context);
},
- child: const Image(image: AssetImage('assets/icon/icon.png'), height: 32),
+ child: const Image(
+ image: AssetImage('assets/icon/icon.png'),
+ height: 32,
+ ),
),
- applicationVersion: '${packageInfo.version} - ${packageInfo.buildNumber}',
+ applicationVersion:
+ '${packageInfo.version} - ${packageInfo.buildNumber}',
children: [
RichText(
text: TextSpan(
@@ -208,7 +241,9 @@ class _DashboardScreenState extends State with WidgetsBindingOb
text: 'www.flaticon.com',
recognizer: TapGestureRecognizer()
..onTap = () {
- launchUrl(Uri.parse('https://www.flaticon.com/'));
+ launchUrl(
+ Uri.parse('https://www.flaticon.com/'),
+ );
},
),
const TextSpan(text: ' made by'),
@@ -234,16 +269,20 @@ class _DashboardScreenState extends State with WidgetsBindingOb
),
)
.toList(),
- )
+ ),
],
);
},
- )
+ ),
].map((choice) {
return PopupMenuItem(
value: choice,
child: Row(
- children: [Icon(choice.icon, color: Colors.black), const SizedBox(width: 8), Text(choice.name)],
+ children: [
+ Icon(choice.icon, color: Colors.black),
+ const SizedBox(width: 8),
+ Text(choice.name),
+ ],
),
);
}).toList();
@@ -252,7 +291,9 @@ class _DashboardScreenState extends State with WidgetsBindingOb
],
),
body: Padding(
- padding: showNextDay ? EdgeInsets.only(bottom: MediaQuery.of(context).size.height / 10) : EdgeInsets.zero,
+ padding: showNextDay
+ ? EdgeInsets.only(bottom: MediaQuery.of(context).size.height / 10)
+ : EdgeInsets.zero,
child: _buildBody(),
),
bottomSheet: showNextDay
@@ -284,14 +325,23 @@ class _DashboardScreenState extends State with WidgetsBindingOb
} else if (subject!.startedAt!.isAfter(DateTime.now())) {
final theme = Theme.of(context);
return Center(
- child: Padding(
- padding: const EdgeInsets.fromLTRB(32, 32, 32, 32),
- child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
- Text(
- AppLocalizations.of(context)!.study_not_started,
- style: TextStyle(fontSize: 20, color: theme.primaryColor, fontWeight: FontWeight.bold),
- )
- ])));
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(32, 32, 32, 32),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ AppLocalizations.of(context)!.study_not_started,
+ style: TextStyle(
+ fontSize: 20,
+ color: theme.primaryColor,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
} else {
return TaskOverview(
subject: subject,
@@ -320,19 +370,31 @@ class StudyFinishedPlaceholder extends StatelessWidget {
children: [
Text(
AppLocalizations.of(context)!.completed_study,
- style: TextStyle(fontSize: 20, color: theme.primaryColor, fontWeight: FontWeight.bold),
+ style: TextStyle(
+ fontSize: 20,
+ color: theme.primaryColor,
+ fontWeight: FontWeight.bold,
+ ),
),
space,
OutlinedButton.icon(
- onPressed: () => Navigator.pushNamed(context, Routes.reportHistory),
+ onPressed: () =>
+ Navigator.pushNamed(context, Routes.reportHistory),
icon: Icon(MdiIcons.history, size: fontSize),
- label: Text(AppLocalizations.of(context)!.report_history, style: textStyle),
+ label: Text(
+ AppLocalizations.of(context)!.report_history,
+ style: textStyle,
+ ),
),
space,
OutlinedButton.icon(
- onPressed: () => Navigator.pushNamed(context, Routes.studySelection),
+ onPressed: () =>
+ Navigator.pushNamed(context, Routes.studySelection),
icon: Icon(MdiIcons.clipboardArrowRightOutline, size: fontSize),
- label: Text(AppLocalizations.of(context)!.study_selection, style: textStyle),
+ label: Text(
+ AppLocalizations.of(context)!.study_selection,
+ style: textStyle,
+ ),
),
],
),
diff --git a/app/lib/screens/study/dashboard/settings.dart b/app/lib/screens/study/dashboard/settings.dart
index 6e0eaafee..485666fb9 100644
--- a/app/lib/screens/study/dashboard/settings.dart
+++ b/app/lib/screens/study/dashboard/settings.dart
@@ -70,29 +70,33 @@ class _SettingsState extends State {
),
],
),
- Row(mainAxisSize: MainAxisSize.min, children: [
- Text('${AppLocalizations.of(context)!.allow_analytics}: '),
- Tooltip(
- triggerMode: TooltipTriggerMode.tap,
- showDuration: const Duration(milliseconds: 10000),
- margin: const EdgeInsets.fromLTRB(30, 0, 30, 0),
- message: AppLocalizations.of(context)!.allow_analytics_desc,
- child: const Icon(
- Icons.info,
+ Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text('${AppLocalizations.of(context)!.allow_analytics}: '),
+ Tooltip(
+ triggerMode: TooltipTriggerMode.tap,
+ showDuration: const Duration(milliseconds: 10000),
+ margin: const EdgeInsets.fromLTRB(30, 0, 30, 0),
+ message: AppLocalizations.of(context)!.allow_analytics_desc,
+ child: const Icon(
+ Icons.info,
+ ),
),
- ),
- const SizedBox(
- width: 5,
- ),
- Switch(
+ const SizedBox(
+ width: 5,
+ ),
+ Switch(
value: _analyticsValue!,
onChanged: (value) {
setState(() {
_analyticsValue = value;
});
AppAnalytics.setEnabled(value);
- }),
- ])
+ },
+ ),
+ ],
+ ),
],
);
}
@@ -123,7 +127,10 @@ class _SettingsState extends State {
backgroundColor: Colors.orange[800],
),
onPressed: () {
- showDialog(context: context, builder: (_) => OptOutAlertDialog(subject: subject));
+ showDialog(
+ context: context,
+ builder: (_) => OptOutAlertDialog(subject: subject),
+ );
},
),
const SizedBox(height: 24),
@@ -132,9 +139,12 @@ class _SettingsState extends State {
label: Text(AppLocalizations.of(context)!.delete_data),
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
onPressed: () {
- showDialog(context: context, builder: (_) => DeleteAlertDialog(subject: subject));
+ showDialog(
+ context: context,
+ builder: (_) => DeleteAlertDialog(subject: subject),
+ );
},
- )
+ ),
],
),
),
@@ -160,7 +170,11 @@ class OptOutAlertDialog extends StatelessWidget {
const TextSpan(text: 'You will lose your progress in '),
TextSpan(
text: subject!.study.title,
- style: TextStyle(color: theme.primaryColor, fontWeight: FontWeight.bold, fontSize: 16),
+ style: TextStyle(
+ color: theme.primaryColor,
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ ),
),
const TextSpan(
text: " and won't be able to recover it. Previously completed "
@@ -179,14 +193,21 @@ class OptOutAlertDialog extends StatelessWidget {
await subject!.softDelete();
await deleteActiveStudyReference();
if (context.mounted) {
- final studyNotifications = context.read().studyNotifications?.flutterLocalNotificationsPlugin;
+ final studyNotifications = context
+ .read()
+ .studyNotifications
+ ?.flutterLocalNotificationsPlugin;
await studyNotifications?.cancelAll();
}
if (context.mounted) {
- Navigator.pushNamedAndRemoveUntil(context, Routes.studySelection, (_) => false);
+ Navigator.pushNamedAndRemoveUntil(
+ context,
+ Routes.studySelection,
+ (_) => false,
+ );
}
},
- )
+ ),
],
);
}
@@ -216,16 +237,22 @@ class DeleteAlertDialog extends StatelessWidget {
await subject!.delete(); // hard-delete
await deleteLocalData();
if (context.mounted) {
- final studyNotifications =
- context.read().studyNotifications?.flutterLocalNotificationsPlugin;
+ final studyNotifications = context
+ .read()
+ .studyNotifications
+ ?.flutterLocalNotificationsPlugin;
await studyNotifications?.cancelAll();
}
if (context.mounted) {
- Navigator.pushNamedAndRemoveUntil(context, Routes.welcome, (_) => false);
+ Navigator.pushNamedAndRemoveUntil(
+ context,
+ Routes.welcome,
+ (_) => false,
+ );
}
} on SocketException catch (_) {}
},
- )
+ ),
],
);
}
diff --git a/app/lib/screens/study/dashboard/task_overview_tab/progress_row.dart b/app/lib/screens/study/dashboard/task_overview_tab/progress_row.dart
index 994d9cb6b..54f8d337a 100644
--- a/app/lib/screens/study/dashboard/task_overview_tab/progress_row.dart
+++ b/app/lib/screens/study/dashboard/task_overview_tab/progress_row.dart
@@ -2,11 +2,10 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+import 'package:studyu_app/util/intervention.dart';
+import 'package:studyu_app/widgets/intervention_card.dart';
import 'package:studyu_core/core.dart';
-import '../../../../util/intervention.dart';
-import '../../../../widgets/intervention_card.dart';
-
class ProgressRow extends StatefulWidget {
final StudySubject? subject;
@@ -21,7 +20,8 @@ class _ProgressRowState extends State {
Widget build(BuildContext context) {
final theme = Theme.of(context);
- final currentPhase = widget.subject!.getInterventionIndexForDate(DateTime.now());
+ final currentPhase =
+ widget.subject!.getInterventionIndexForDate(DateTime.now());
return Padding(
padding: const EdgeInsets.all(8),
@@ -39,17 +39,25 @@ class _ProgressRowState extends State {
indent: 5,
endIndent: 5,
thickness: 3,
- color: currentPhase > index ? theme.primaryColor : theme.disabledColor,
+ color: currentPhase > index
+ ? theme.primaryColor
+ : theme.disabledColor,
),
),
- widget.subject!.getInterventionsInOrder().asMap().entries.map((entry) {
+ widget.subject!
+ .getInterventionsInOrder()
+ .asMap()
+ .entries
+ .map((entry) {
return InterventionSegment(
intervention: entry.value,
isCurrent: currentPhase == entry.key,
isFuture: currentPhase < entry.key,
phaseDuration: widget.subject!.study.schedule.phaseDuration,
- percentCompleted: widget.subject!.percentCompletedForPhase(entry.key),
- percentMissed: widget.subject!.percentMissedForPhase(entry.key, DateTime.now()),
+ percentCompleted:
+ widget.subject!.percentCompletedForPhase(entry.key),
+ percentMissed: widget.subject!
+ .percentMissedForPhase(entry.key, DateTime.now()),
);
}),
),
@@ -96,7 +104,7 @@ class InterventionSegment extends StatelessWidget {
width: 8,
height: 10,
color: Colors.white,
- )
+ ),
],
),
),
@@ -109,10 +117,13 @@ class InterventionSegment extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
- final color = isFuture ? Colors.grey : (isCurrent ? theme.colorScheme.secondary : theme.primaryColor);
+ final color = isFuture
+ ? Colors.grey
+ : (isCurrent ? theme.colorScheme.secondary : theme.primaryColor);
final emptyColor = Color.alphaBlend(theme.dividerColor, Colors.white);
- final activeColor = Color.alphaBlend(theme.colorScheme.secondary, Colors.white);
+ final activeColor =
+ Color.alphaBlend(theme.colorScheme.secondary, Colors.white);
final completedColor = Color.alphaBlend(theme.primaryColor, Colors.white);
return Expanded(
@@ -163,7 +174,9 @@ class InterventionSegment extends StatelessWidget {
},
elevation: 0,
fillColor: color,
- shape: const CircleBorder(side: BorderSide(color: Colors.white, width: 2)),
+ shape: const CircleBorder(
+ side: BorderSide(color: Colors.white, width: 2),
+ ),
child: interventionIcon(intervention),
),
],
@@ -172,7 +185,10 @@ class InterventionSegment extends StatelessWidget {
}
}
-Iterable intersperseIndexed(T Function(int) generator, Iterable iterable) sync* {
+Iterable intersperseIndexed(
+ T Function(int) generator,
+ Iterable iterable,
+) sync* {
final iterator = iterable.iterator;
var index = 0;
if (iterator.moveNext()) {
diff --git a/app/lib/screens/study/dashboard/task_overview_tab/task_box.dart b/app/lib/screens/study/dashboard/task_overview_tab/task_box.dart
index 9dbd18b3c..6b659f621 100644
--- a/app/lib/screens/study/dashboard/task_overview_tab/task_box.dart
+++ b/app/lib/screens/study/dashboard/task_overview_tab/task_box.dart
@@ -1,14 +1,13 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/screens/study/tasks/task_screen.dart';
+import 'package:studyu_app/theme.dart';
import 'package:studyu_app/util/schedule_notifications.dart';
+import 'package:studyu_app/widgets/round_checkbox.dart';
import 'package:studyu_core/core.dart';
-import '../../../../models/app_state.dart';
-import '../../../../theme.dart';
-import '../../../../widgets/round_checkbox.dart';
-import '../../tasks/task_screen.dart';
-
class TaskBox extends StatefulWidget {
final TaskInstance taskInstance;
final Icon icon;
@@ -29,7 +28,9 @@ class _TaskBoxState extends State {
Future _navigateToTaskScreen() async {
await Navigator.push(
context,
- MaterialPageRoute(builder: (context) => TaskScreen(taskInstance: widget.taskInstance)),
+ MaterialPageRoute(
+ builder: (context) => TaskScreen(taskInstance: widget.taskInstance),
+ ),
);
widget.onCompleted();
// Rebuild widget
@@ -39,17 +40,20 @@ class _TaskBoxState extends State {
@override
Widget build(BuildContext context) {
- final completed = context
- .watch()
- .activeSubject!
- .completedTaskInstanceForDay(widget.taskInstance.task.id, widget.taskInstance.completionPeriod, DateTime.now());
+ final completed =
+ context.watch().activeSubject!.completedTaskInstanceForDay(
+ widget.taskInstance.task.id,
+ widget.taskInstance.completionPeriod,
+ DateTime.now(),
+ );
final isPreview = context.read().isPreview;
- final isInsidePeriod = widget.taskInstance.completionPeriod.contains(StudyUTimeOfDay.now());
+ final isInsidePeriod =
+ widget.taskInstance.completionPeriod.contains(StudyUTimeOfDay.now());
final isTaskOpen = !completed && isInsidePeriod || isPreview || kDebugMode;
return Card(
elevation: 2,
child: InkWell(
- onTap: (isTaskOpen) ? _navigateToTaskScreen : () {},
+ onTap: isTaskOpen ? _navigateToTaskScreen : () {},
child: Row(
children: [
Expanded(
@@ -62,13 +66,14 @@ class _TaskBoxState extends State {
if (isInsidePeriod || isPreview || completed)
RoundCheckbox(
value: completed, //_isCompleted,
- onChanged: (value) => isTaskOpen ? _navigateToTaskScreen() : () {},
+ onChanged: (value) =>
+ isTaskOpen ? _navigateToTaskScreen() : () {},
)
else
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 8, 0),
child: Icon(Icons.lock, color: theme.colorScheme.secondary),
- )
+ ),
],
),
),
diff --git a/app/lib/screens/study/dashboard/task_overview_tab/task_overview.dart b/app/lib/screens/study/dashboard/task_overview_tab/task_overview.dart
index 6c3fd9529..c43a28a49 100644
--- a/app/lib/screens/study/dashboard/task_overview_tab/task_overview.dart
+++ b/app/lib/screens/study/dashboard/task_overview_tab/task_overview.dart
@@ -1,20 +1,24 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/screens/study/dashboard/task_overview_tab/progress_row.dart';
+import 'package:studyu_app/screens/study/dashboard/task_overview_tab/task_box.dart';
import 'package:studyu_app/theme.dart';
+import 'package:studyu_app/widgets/intervention_card.dart';
import 'package:studyu_core/core.dart';
-import '../../../../routes.dart';
-import '../../../../widgets/intervention_card.dart';
-import 'progress_row.dart';
-import 'task_box.dart';
-
class TaskOverview extends StatefulWidget {
final StudySubject? subject;
final List? scheduleToday;
final String? interventionIcon;
- const TaskOverview({required this.subject, required this.scheduleToday, super.key, this.interventionIcon});
+ const TaskOverview({
+ required this.subject,
+ required this.scheduleToday,
+ super.key,
+ this.interventionIcon,
+ });
@override
State createState() => _TaskOverviewState();
@@ -24,7 +28,11 @@ class _TaskOverviewState extends State {
void _navigateToReportIfStudyCompleted(BuildContext context) {
if (widget.subject!.completedStudy) {
// Workaround to reload dashboard
- Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (_) => false);
+ Navigator.pushNamedAndRemoveUntil(
+ context,
+ Routes.dashboard,
+ (_) => false,
+ );
}
}
@@ -42,7 +50,8 @@ class _TaskOverviewState extends State {
const SizedBox(width: 8),
Text(
taskInstance.completionPeriod.formatted(),
- style: theme.textTheme.titleSmall!.copyWith(fontSize: 16, color: theme.primaryColor),
+ style: theme.textTheme.titleSmall!
+ .copyWith(fontSize: 16, color: theme.primaryColor),
),
],
),
@@ -80,19 +89,28 @@ class _TaskOverviewState extends State {
Row(
children: [
Expanded(
- child:
- Text(AppLocalizations.of(context)!.intervention_current, style: theme.textTheme.titleLarge)),
+ child: Text(
+ AppLocalizations.of(context)!.intervention_current,
+ style: theme.textTheme.titleLarge,
+ ),
+ ),
const Spacer(),
Text(
'${widget.subject!.daysLeftForPhase(widget.subject!.getInterventionIndexForDate(DateTime.now()))} ${AppLocalizations.of(context)!.days_left}',
style: const TextStyle(color: primaryColor),
- )
+ ),
],
),
const SizedBox(height: 8),
- InterventionCardTitle(intervention: widget.subject!.getInterventionForDate(DateTime.now())),
+ InterventionCardTitle(
+ intervention:
+ widget.subject!.getInterventionForDate(DateTime.now()),
+ ),
const SizedBox(height: 8),
- Text(AppLocalizations.of(context)!.today_tasks, style: theme.textTheme.titleLarge)
+ Text(
+ AppLocalizations.of(context)!.today_tasks,
+ style: theme.textTheme.titleLarge,
+ ),
],
),
),
diff --git a/app/lib/screens/study/multimodal/capture_picture_screen.dart b/app/lib/screens/study/multimodal/capture_picture_screen.dart
index 28178e527..547940347 100644
--- a/app/lib/screens/study/multimodal/capture_picture_screen.dart
+++ b/app/lib/screens/study/multimodal/capture_picture_screen.dart
@@ -10,13 +10,18 @@ class CapturePictureScreen extends StatefulWidget {
final String userId;
final String studyId;
- const CapturePictureScreen({super.key, required this.userId, required this.studyId});
+ const CapturePictureScreen({
+ super.key,
+ required this.userId,
+ required this.studyId,
+ });
@override
State createState() => _CapturePictureScreenState();
}
-class _CapturePictureScreenState extends State with WidgetsBindingObserver {
+class _CapturePictureScreenState extends State
+ with WidgetsBindingObserver {
CameraController? _cameraController;
List? _cameras;
int _jumpToNextCameraTaps = 0;
@@ -39,7 +44,7 @@ class _CapturePictureScreenState extends State with Widget
}
@override
- void didChangeAppLifecycleState(AppLifecycleState state) async {
+ Future didChangeAppLifecycleState(AppLifecycleState state) async {
final CameraController? cameraController = _cameraController;
// App state changed before we got the chance to initialize.
@@ -85,10 +90,8 @@ class _CapturePictureScreenState extends State with Widget
case 'CameraAccessDeniedWithoutPrompt':
case 'CameraAccessRestricted':
errorText = AppLocalizations.of(context)!.camera_access_denied;
- break;
case 'NoCameraAvailable':
errorText = AppLocalizations.of(context)!.no_camera_available;
- break;
}
}
@@ -103,9 +106,11 @@ class _CapturePictureScreenState extends State with Widget
Future> _getAvailableCameras() async {
return (await availableCameras())
- .where((CameraDescription aCameraDescription) =>
- aCameraDescription.lensDirection == CameraLensDirection.back ||
- aCameraDescription.lensDirection == CameraLensDirection.front)
+ .where(
+ (CameraDescription aCameraDescription) =>
+ aCameraDescription.lensDirection == CameraLensDirection.back ||
+ aCameraDescription.lensDirection == CameraLensDirection.front,
+ )
.toList();
}
@@ -116,7 +121,9 @@ class _CapturePictureScreenState extends State with Widget
Future _tryCapturePicture() async {
final cameraController = _cameraController;
- if (cameraController == null || !cameraController.value.isInitialized || cameraController.value.isTakingPicture) {
+ if (cameraController == null ||
+ !cameraController.value.isInitialized ||
+ cameraController.value.isTakingPicture) {
return;
}
@@ -155,59 +162,64 @@ class _CapturePictureScreenState extends State with Widget
Widget build(BuildContext context) {
final cameraController = _cameraController;
return Scaffold(
- appBar: AppBar(title: Text(AppLocalizations.of(context)!.take_a_photo)),
- body: cameraController == null
- ? const Center(child: CircularProgressIndicator())
- : Stack(
- children: [
- CameraPreview(cameraController),
- _isTakingPicture
- ? Container(
- color: Colors.black.withOpacity(0.5),
- alignment: Alignment.center,
- child: Container(
- decoration: BoxDecoration(
- color: Theme.of(context).dialogBackgroundColor,
- borderRadius: BorderRadius.circular(10),
- ),
- padding: const EdgeInsets.all(20.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const CircularProgressIndicator(),
- const SizedBox(height: 16),
- Text(AppLocalizations.of(context)!.take_a_photo),
- ],
- ),
+ appBar: AppBar(title: Text(AppLocalizations.of(context)!.take_a_photo)),
+ body: cameraController == null
+ ? const Center(child: CircularProgressIndicator())
+ : Stack(
+ children: [
+ CameraPreview(cameraController),
+ if (_isTakingPicture)
+ Container(
+ color: Colors.black.withOpacity(0.5),
+ alignment: Alignment.center,
+ child: Container(
+ decoration: BoxDecoration(
+ color: Theme.of(context).dialogBackgroundColor,
+ borderRadius: BorderRadius.circular(10),
+ ),
+ padding: const EdgeInsets.all(20.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const CircularProgressIndicator(),
+ const SizedBox(height: 16),
+ Text(
+ AppLocalizations.of(context)!.take_a_photo,
),
- )
- : const SizedBox.shrink(),
- ],
- ),
- floatingActionButton: Wrap(
- direction: Axis.horizontal,
- children: [
- Container(
- margin: const EdgeInsets.all(10),
- child: FloatingActionButton(
- heroTag: "captureImage",
- onPressed: cameraController != null && !_isTakingPicture
- ? () async {
- await _tryCapturePicture();
- }
- : null,
- child: const Icon(Icons.camera_alt),
- ),
+ ],
+ ),
+ ),
+ )
+ else
+ const SizedBox.shrink(),
+ ],
),
- Container(
- margin: const EdgeInsets.all(10),
- child: FloatingActionButton(
- heroTag: "jumpToNextCamera",
- onPressed: cameraController != null && !_isTakingPicture ? () async => await _jumpToNextCamera() : null,
- child: const Icon(Icons.autorenew),
- ),
- )
- ],
- ));
+ floatingActionButton: Wrap(
+ children: [
+ Container(
+ margin: const EdgeInsets.all(10),
+ child: FloatingActionButton(
+ heroTag: "captureImage",
+ onPressed: cameraController != null && !_isTakingPicture
+ ? () async {
+ await _tryCapturePicture();
+ }
+ : null,
+ child: const Icon(Icons.camera_alt),
+ ),
+ ),
+ Container(
+ margin: const EdgeInsets.all(10),
+ child: FloatingActionButton(
+ heroTag: "jumpToNextCamera",
+ onPressed: cameraController != null && !_isTakingPicture
+ ? () async => await _jumpToNextCamera()
+ : null,
+ child: const Icon(Icons.autorenew),
+ ),
+ ),
+ ],
+ ),
+ );
}
}
diff --git a/app/lib/screens/study/onboarding/consent.dart b/app/lib/screens/study/onboarding/consent.dart
index f297edda8..f45b2ffcb 100644
--- a/app/lib/screens/study/onboarding/consent.dart
+++ b/app/lib/screens/study/onboarding/consent.dart
@@ -6,15 +6,14 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/screens/study/onboarding/onboarding_progress.dart';
+import 'package:studyu_app/util/save_pdf.dart';
+import 'package:studyu_app/widgets/bottom_onboarding_navigation.dart';
import 'package:studyu_app/widgets/html_text.dart';
import 'package:studyu_core/core.dart';
-import '../../../models/app_state.dart';
-import '../../../routes.dart';
-import '../../../util/save_pdf.dart';
-import '../../../widgets/bottom_onboarding_navigation.dart';
-import 'onboarding_progress.dart';
-
class ConsentScreen extends StatefulWidget {
const ConsentScreen({super.key});
@@ -43,15 +42,23 @@ class _ConsentScreenState extends State {
}
Future> generatePdfContent() async {
- final ttf = pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Regular.ttf'));
+ final ttf =
+ pw.Font.ttf(await rootBundle.load('assets/fonts/Roboto-Regular.ttf'));
return consentList
.map(
(consentItem) => [
pw.Header(
level: 0,
- child: pw.Text(consentItem.title ?? '', textScaleFactor: 2, style: pw.TextStyle(font: ttf)),
+ child: pw.Text(
+ consentItem.title ?? '',
+ textScaleFactor: 2,
+ style: pw.TextStyle(font: ttf),
+ ),
+ ),
+ pw.Paragraph(
+ text: consentItem.description ?? '',
+ style: pw.TextStyle(font: ttf),
),
- pw.Paragraph(text: consentItem.description ?? '', style: pw.TextStyle(font: ttf)),
],
)
.expand((element) => element)
@@ -75,19 +82,29 @@ class _ConsentScreenState extends State {
context: context,
builder: (context) => AlertDialog(
elevation: 24,
- title: Text(AppLocalizations.of(context)!.save_not_supported),
- content: Text(AppLocalizations.of(context)!.save_not_supported_description),
+ title:
+ Text(AppLocalizations.of(context)!.save_not_supported),
+ content: Text(
+ AppLocalizations.of(context)!
+ .save_not_supported_description,
+ ),
),
);
}
final pdfContent = await generatePdfContent();
if (!context.mounted) return;
- final savedFilePath = await savePDF(context, '${subject!.study.title}_consent', pdfContent);
+ final savedFilePath = await savePDF(
+ context,
+ '${subject!.study.title}_consent',
+ pdfContent,
+ );
if (savedFilePath != null) {
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
- content: Text('${AppLocalizations.of(context)!.was_saved_to}$savedFilePath.'),
+ content: Text(
+ '${AppLocalizations.of(context)!.was_saved_to}$savedFilePath.',
+ ),
),
);
}
@@ -114,23 +131,29 @@ class _ConsentScreenState extends State {
style: theme.textTheme.titleMedium,
),
TextSpan(
- text: AppLocalizations.of(context)!.please_give_consent_why,
- style: theme.textTheme.titleSmall!.copyWith(color: theme.primaryColor),
+ text: AppLocalizations.of(context)!
+ .please_give_consent_why,
+ style: theme.textTheme.titleSmall!
+ .copyWith(color: theme.primaryColor),
recognizer: TapGestureRecognizer()
..onTap = () => showDialog(
context: context,
builder: (context) => AlertDialog(
- content: Text(AppLocalizations.of(context)!.please_give_consent_reason),
+ content: Text(
+ AppLocalizations.of(context)!
+ .please_give_consent_reason,
+ ),
),
),
- )
+ ),
],
),
),
Flexible(
child: GridView.builder(
shrinkWrap: true,
- gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+ gridDelegate:
+ const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
@@ -156,10 +179,15 @@ class _ConsentScreenState extends State {
bottomNavigationBar: BottomOnboardingNavigation(
backLabel: AppLocalizations.of(context)!.decline,
backIcon: const Icon(Icons.close),
- onBack: () => Navigator.popUntil(context, ModalRoute.withName(Routes.studySelection)),
+ onBack: () => Navigator.popUntil(
+ context,
+ ModalRoute.withName(Routes.studySelection),
+ ),
nextLabel: AppLocalizations.of(context)!.accept,
nextIcon: const Icon(Icons.check),
- onNext: boxLogic.every((element) => element) || kDebugMode ? () => Navigator.pop(context, true) : null,
+ onNext: boxLogic.every((element) => element) || kDebugMode
+ ? () => Navigator.pop(context, true)
+ : null,
progress: const OnboardingProgress(stage: 2, progress: 2.5),
),
);
@@ -172,7 +200,13 @@ class ConsentCard extends StatelessWidget {
final Function(int) onTapped;
final bool? isChecked;
- const ConsentCard({super.key, this.consent, this.index, required this.onTapped, this.isChecked});
+ const ConsentCard({
+ super.key,
+ this.consent,
+ this.index,
+ required this.onTapped,
+ this.isChecked,
+ });
@override
Widget build(BuildContext context) {
@@ -194,15 +228,22 @@ class ConsentCard extends StatelessWidget {
builder: (context) => AlertDialog(
title: Row(
children: [
- consent!.iconName.isNotEmpty
- ? Icon(MdiIcons.fromString(consent!.iconName), color: theme.primaryColor)
- : const SizedBox.shrink(),
- consent!.iconName.isNotEmpty ? const SizedBox(width: 8) : const SizedBox.shrink(),
+ if (consent!.iconName.isNotEmpty)
+ Icon(
+ MdiIcons.fromString(consent!.iconName),
+ color: theme.primaryColor,
+ )
+ else
+ const SizedBox.shrink(),
+ if (consent!.iconName.isNotEmpty)
+ const SizedBox(width: 8)
+ else
+ const SizedBox.shrink(),
Expanded(child: Text(consent!.title!)),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
- )
+ ),
],
),
content: HtmlText(consent!.description),
@@ -216,10 +257,18 @@ class ConsentCard extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
- consent!.iconName.isNotEmpty
- ? Icon(MdiIcons.fromString(consent!.iconName), size: 60, color: Colors.blue)
- : const SizedBox.shrink(),
- consent!.iconName.isNotEmpty ? const SizedBox(height: 10) : const SizedBox.shrink(),
+ if (consent!.iconName.isNotEmpty)
+ Icon(
+ MdiIcons.fromString(consent!.iconName),
+ size: 60,
+ color: Colors.blue,
+ )
+ else
+ const SizedBox.shrink(),
+ if (consent!.iconName.isNotEmpty)
+ const SizedBox(height: 10)
+ else
+ const SizedBox.shrink(),
Flexible(
child: Text(
consent!.title!,
@@ -242,5 +291,10 @@ class ConsentElement {
final String acknowledgmentText;
final IconData icon;
- ConsentElement(this.title, this.descriptionText, this.acknowledgmentText, this.icon);
+ ConsentElement(
+ this.title,
+ this.descriptionText,
+ this.acknowledgmentText,
+ this.icon,
+ );
}
diff --git a/app/lib/screens/study/onboarding/eligibility_screen.dart b/app/lib/screens/study/onboarding/eligibility_screen.dart
index 22bf5f07b..c0325f2b6 100644
--- a/app/lib/screens/study/onboarding/eligibility_screen.dart
+++ b/app/lib/screens/study/onboarding/eligibility_screen.dart
@@ -2,12 +2,11 @@ import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
+import 'package:studyu_app/screens/study/onboarding/onboarding_progress.dart';
+import 'package:studyu_app/widgets/bottom_onboarding_navigation.dart';
+import 'package:studyu_app/widgets/questionnaire/questionnaire_widget.dart';
import 'package:studyu_core/core.dart';
-import '../../../widgets/bottom_onboarding_navigation.dart';
-import '../../../widgets/questionnaire/questionnaire_widget.dart';
-import 'onboarding_progress.dart';
-
class EligibilityResult {
final bool eligible;
final QuestionnaireState answers;
@@ -19,7 +18,10 @@ class EligibilityResult {
class EligibilityScreen extends StatefulWidget {
final Study? study;
- static MaterialPageRoute routeFor({required Study? study}) => MaterialPageRoute(
+ static MaterialPageRoute routeFor({
+ required Study? study,
+ }) =>
+ MaterialPageRoute(
builder: (_) => EligibilityScreen(study: study),
settings: const RouteSettings(name: '/eligibilityCheck'),
);
@@ -48,13 +50,15 @@ class _EligibilityScreenState extends State {
bool _checkContinuation(QuestionnaireState qs) {
final criteria = widget.study!.eligibilityCriteria;
- EligibilityCriterion? failingResult = criteria.firstWhereOrNull((element) => element.isViolated(qs));
+ EligibilityCriterion? failingResult =
+ criteria.firstWhereOrNull((element) => element.isViolated(qs));
if (failingResult == null) return true;
// freetext quickfix start
failingResult = _isFreeTextCriterion(failingResult) ? null : failingResult;
// freetext quickfix end
setState(() {
- activeResult = EligibilityResult(qs, eligible: false, firstFailed: failingResult);
+ activeResult =
+ EligibilityResult(qs, eligible: false, firstFailed: failingResult);
});
return false;
}
@@ -73,8 +77,13 @@ class _EligibilityScreenState extends State {
if (conditionResult) {
activeResult = EligibilityResult(qs, eligible: conditionResult);
} else {
- final firstFailed = criteria.firstWhere((criterion) => criterion.isViolated(qs));
- activeResult = EligibilityResult(qs, eligible: conditionResult, firstFailed: firstFailed);
+ final firstFailed =
+ criteria.firstWhere((criterion) => criterion.isViolated(qs));
+ activeResult = EligibilityResult(
+ qs,
+ eligible: conditionResult,
+ firstFailed: firstFailed,
+ );
}
});
}
@@ -84,7 +93,8 @@ class _EligibilityScreenState extends State {
bool _isFreeTextCriterion(EligibilityCriterion criterion) {
return widget.study?.questionnaire.questions.any((element) {
if (criterion.condition.type == ChoiceExpression.expressionType) {
- ChoiceExpression choiceExpression = criterion.condition as ChoiceExpression;
+ final ChoiceExpression choiceExpression =
+ criterion.condition as ChoiceExpression;
return element.id == choiceExpression.target!;
}
return false;
@@ -102,7 +112,10 @@ class _EligibilityScreenState extends State {
color: Colors.green,
size: 32,
),
- content: Text(AppLocalizations.of(context)!.eligible_yes, style: Theme.of(context).textTheme.titleMedium),
+ content: Text(
+ AppLocalizations.of(context)!.eligible_yes,
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
actions: [Container()],
forceActionsBelow: true,
backgroundColor: Colors.green[50],
@@ -117,13 +130,19 @@ class _EligibilityScreenState extends State {
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Text(AppLocalizations.of(context)!.eligible_no, style: Theme.of(context).textTheme.titleMedium),
+ Text(
+ AppLocalizations.of(context)!.eligible_no,
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
const SizedBox(height: 4),
if (activeResult?.firstFailed?.reason != null)
Text(activeResult!.firstFailed!.reason!)
else
const SizedBox.shrink(),
- if (activeResult?.firstFailed?.reason != null) const SizedBox(height: 4) else const SizedBox.shrink(),
+ if (activeResult?.firstFailed?.reason != null)
+ const SizedBox(height: 4)
+ else
+ const SizedBox.shrink(),
Text(AppLocalizations.of(context)!.eligible_mistake),
],
),
@@ -131,20 +150,22 @@ class _EligibilityScreenState extends State {
TextButton(
onPressed: _finish,
child: Text(AppLocalizations.of(context)!.eligible_back),
- )
+ ),
],
forceActionsBelow: true,
backgroundColor: Colors.red[50],
);
- Widget _constructResultBanner() => activeResult!.eligible ? _constructPassBanner() : _constructFailBanner();
+ Widget _constructResultBanner() =>
+ activeResult!.eligible ? _constructPassBanner() : _constructFailBanner();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(
- title: Text(AppLocalizations.of(context)!.eligibility_questionnaire_title),
+ title:
+ Text(AppLocalizations.of(context)!.eligibility_questionnaire_title),
leading: Icon(MdiIcons.clipboardList),
),
body: Column(
diff --git a/app/lib/screens/study/onboarding/intervention_selection.dart b/app/lib/screens/study/onboarding/intervention_selection.dart
index e9c70d938..b2261a423 100644
--- a/app/lib/screens/study/onboarding/intervention_selection.dart
+++ b/app/lib/screens/study/onboarding/intervention_selection.dart
@@ -2,23 +2,24 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/screens/study/onboarding/onboarding_progress.dart';
+import 'package:studyu_app/widgets/bottom_onboarding_navigation.dart';
+import 'package:studyu_app/widgets/intervention_card.dart';
import 'package:studyu_core/core.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
-import '../../../models/app_state.dart';
-import '../../../routes.dart';
-import '../../../widgets/bottom_onboarding_navigation.dart';
-import '../../../widgets/intervention_card.dart';
-import 'onboarding_progress.dart';
-
class InterventionSelectionScreen extends StatefulWidget {
const InterventionSelectionScreen({super.key});
@override
- State createState() => _InterventionSelectionScreenState();
+ State createState() =>
+ _InterventionSelectionScreenState();
}
-class _InterventionSelectionScreenState extends State {
+class _InterventionSelectionScreenState
+ extends State {
final List selectedInterventionIds = [];
Study? selectedStudy;
@@ -39,8 +40,10 @@ class _InterventionSelectionScreenState extends State interventionId == interventions[index].id),
+ selected: selectedInterventionIds.any(
+ (interventionId) => interventionId == interventions[index].id,
+ ),
onTap: () => onSelect(interventions[index].id),
),
),
@@ -73,7 +78,9 @@ class _InterventionSelectionScreenState extends State 2) selectedInterventionIds.removeAt(0);
+ if (selectedInterventionIds.length > 2) {
+ selectedInterventionIds.removeAt(0);
+ }
} else {
selectedInterventionIds.removeWhere((id) => id == interventionId);
}
@@ -116,7 +123,10 @@ class _InterventionSelectionScreenState extends State {
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
- content: Text(AppLocalizations.of(context)!.user_did_not_give_consent),
+ content:
+ Text(AppLocalizations.of(context)!.user_did_not_give_consent),
duration: const Duration(seconds: 30),
),
);
@@ -94,8 +94,12 @@ class Timeline extends StatelessWidget {
return InterventionTile(
title: intervention.name,
iconName: intervention.icon,
- color: intervention.isBaseline() ? Colors.grey : theme.colorScheme.secondary,
- date: now.add(Duration(days: index * subject!.study.schedule.phaseDuration)),
+ color: intervention.isBaseline()
+ ? Colors.grey
+ : theme.colorScheme.secondary,
+ date: now.add(
+ Duration(days: index * subject!.study.schedule.phaseDuration),
+ ),
isFirst: index == 0,
);
}),
@@ -105,7 +109,7 @@ class Timeline extends StatelessWidget {
color: Colors.green,
isLast: true,
date: subject!.endDate(now),
- )
+ ),
],
);
}
@@ -146,10 +150,17 @@ class InterventionTile extends StatelessWidget {
beforeLineStyle: LineStyle(color: theme.primaryColor),
afterLineStyle: LineStyle(color: theme.primaryColor),
endChild: TimelineChild(
- child: Text(title!, style: theme.textTheme.titleLarge!.copyWith(color: theme.primaryColor)),
+ child: Text(
+ title!,
+ style:
+ theme.textTheme.titleLarge!.copyWith(color: theme.primaryColor),
+ ),
),
startChild: TimelineChild(
- child: Text(DateFormat('dd-MM-yyyy').format(date), style: const TextStyle(fontWeight: FontWeight.bold)),
+ child: Text(
+ DateFormat('dd-MM-yyyy').format(date),
+ style: const TextStyle(fontWeight: FontWeight.bold),
+ ),
),
);
}
@@ -164,7 +175,10 @@ class IconIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DecoratedBox(
- decoration: BoxDecoration(shape: BoxShape.circle, color: color ?? Theme.of(context).colorScheme.secondary),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: color ?? Theme.of(context).colorScheme.secondary,
+ ),
child: Center(
child: Icon(MdiIcons.fromString(iconName), color: Colors.white),
),
diff --git a/app/lib/screens/study/onboarding/kickoff.dart b/app/lib/screens/study/onboarding/kickoff.dart
index b46b652aa..d59c681a9 100644
--- a/app/lib/screens/study/onboarding/kickoff.dart
+++ b/app/lib/screens/study/onboarding/kickoff.dart
@@ -2,13 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
import 'package:studyu_app/util/cache.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
-import '../../../models/app_state.dart';
-import '../../../routes.dart';
-
class KickoffScreen extends StatefulWidget {
const KickoffScreen({super.key});
@@ -33,7 +32,11 @@ class _KickoffScreen extends State {
await storeActiveSubjectId(subject!.id);
if (!context.mounted) return;
setState(() => ready = true);
- Navigator.pushNamedAndRemoveUntil(context, Routes.dashboard, (_) => false);
+ Navigator.pushNamedAndRemoveUntil(
+ context,
+ Routes.dashboard,
+ (_) => false,
+ );
} catch (e) {
StudyULogger.fatal('Failed creating subject: $e');
}
@@ -58,8 +61,9 @@ class _KickoffScreen extends State {
size: 64,
);
- String _getStatusText(BuildContext context) =>
- !ready ? AppLocalizations.of(context)!.setting_up_study : AppLocalizations.of(context)!.good_to_go;
+ String _getStatusText(BuildContext context) => !ready
+ ? AppLocalizations.of(context)!.setting_up_study
+ : AppLocalizations.of(context)!.good_to_go;
@override
Widget build(BuildContext context) {
diff --git a/app/lib/screens/study/onboarding/onboarding_progress.dart b/app/lib/screens/study/onboarding/onboarding_progress.dart
index 97556a10b..12bfb89fa 100644
--- a/app/lib/screens/study/onboarding/onboarding_progress.dart
+++ b/app/lib/screens/study/onboarding/onboarding_progress.dart
@@ -4,7 +4,11 @@ class OnboardingProgress extends StatelessWidget {
final int stage;
final double progress;
- const OnboardingProgress({required this.stage, required this.progress, super.key});
+ const OnboardingProgress({
+ required this.stage,
+ required this.progress,
+ super.key,
+ });
double _getProgressForStage(int stage) {
if (stage < this.stage) return 1;
diff --git a/app/lib/screens/study/onboarding/study_overview.dart b/app/lib/screens/study/onboarding/study_overview.dart
index 16d4148e9..732aa6ae1 100644
--- a/app/lib/screens/study/onboarding/study_overview.dart
+++ b/app/lib/screens/study/onboarding/study_overview.dart
@@ -2,16 +2,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/screens/study/dashboard/contact_tab/contact_screen.dart';
+import 'package:studyu_app/screens/study/onboarding/eligibility_screen.dart';
+import 'package:studyu_app/widgets/bottom_onboarding_navigation.dart';
+import 'package:studyu_app/widgets/study_tile.dart';
import 'package:studyu_core/core.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
-import '../../../models/app_state.dart';
-import '../../../routes.dart';
-import '../../../widgets/bottom_onboarding_navigation.dart';
-import '../../../widgets/study_tile.dart';
-import '../dashboard/contact_tab/contact_screen.dart';
-import 'eligibility_screen.dart';
-
class StudyOverviewScreen extends StatefulWidget {
const StudyOverviewScreen({super.key});
@@ -54,7 +53,10 @@ class _StudyOverviewScreen extends State {
Future navigateToEligibilityCheck(BuildContext context) async {
final study = context.read().selectedStudy;
- final result = await Navigator.push(context, EligibilityScreen.routeFor(study: study));
+ final result = await Navigator.push(
+ context,
+ EligibilityScreen.routeFor(study: study),
+ );
if (result == null) return;
if (!context.mounted) return;
@@ -103,20 +105,31 @@ class StudyDetailsView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
- final baselineLength = study!.schedule.includeBaseline ? study!.schedule.phaseDuration : 0;
+ final baselineLength =
+ study!.schedule.includeBaseline ? study!.schedule.phaseDuration : 0;
final studyLength = baselineLength +
- study!.schedule.phaseDuration * study!.schedule.numberOfCycles * StudySchedule.numberOfInterventions;
+ study!.schedule.phaseDuration *
+ study!.schedule.numberOfCycles *
+ StudySchedule.numberOfInterventions;
return Column(
children: [
ListTile(
- title: Text(AppLocalizations.of(context)!.intervention_phase_duration),
- subtitle: Text('${study!.schedule.phaseDuration} ${AppLocalizations.of(context)!.days}'),
- leading: Icon(MdiIcons.clock, color: theme.primaryColor, size: iconSize),
+ title:
+ Text(AppLocalizations.of(context)!.intervention_phase_duration),
+ subtitle: Text(
+ '${study!.schedule.phaseDuration} ${AppLocalizations.of(context)!.days}',
+ ),
+ leading:
+ Icon(MdiIcons.clock, color: theme.primaryColor, size: iconSize),
),
ListTile(
title: Text(AppLocalizations.of(context)!.study_length),
subtitle: Text('$studyLength ${AppLocalizations.of(context)!.days}'),
- leading: Icon(MdiIcons.calendar, color: theme.primaryColor, size: iconSize),
+ leading: Icon(
+ MdiIcons.calendar,
+ color: theme.primaryColor,
+ size: iconSize,
+ ),
),
const SizedBox(height: 16),
ContactWidget(
diff --git a/app/lib/screens/study/onboarding/study_selection.dart b/app/lib/screens/study/onboarding/study_selection.dart
index 35f072a74..8a4899540 100644
--- a/app/lib/screens/study/onboarding/study_selection.dart
+++ b/app/lib/screens/study/onboarding/study_selection.dart
@@ -5,15 +5,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/widgets/bottom_onboarding_navigation.dart';
+import 'package:studyu_app/widgets/study_tile.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
-import '../../../models/app_state.dart';
-import '../../../routes.dart';
-import '../../../widgets/bottom_onboarding_navigation.dart';
-import '../../../widgets/study_tile.dart';
-
Future navigateToStudyOverview(
BuildContext context,
Study study, {
@@ -30,7 +29,8 @@ Future showAppOutdatedDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (context) => AlertDialog(
- title: Text(AppLocalizations.of(context)!.study_selection_unsupported_title),
+ title:
+ Text(AppLocalizations.of(context)!.study_selection_unsupported_title),
content: Text(AppLocalizations.of(context)!.study_selection_unsupported),
actions: [
TextButton(
@@ -74,7 +74,8 @@ class _StudySelectionScreenState extends State {
text: TextSpan(
children: [
TextSpan(
- text: AppLocalizations.of(context)!.study_selection_single,
+ text: AppLocalizations.of(context)!
+ .study_selection_single,
style: theme.textTheme.titleSmall,
),
TextSpan(
@@ -82,52 +83,64 @@ class _StudySelectionScreenState extends State {
style: theme.textTheme.titleSmall,
),
TextSpan(
- text: AppLocalizations.of(context)!.study_selection_single_why,
- style: theme.textTheme.titleSmall!.copyWith(color: theme.primaryColor),
+ text: AppLocalizations.of(context)!
+ .study_selection_single_why,
+ style: theme.textTheme.titleSmall!
+ .copyWith(color: theme.primaryColor),
recognizer: TapGestureRecognizer()
..onTap = () => showDialog(
context: context,
builder: (context) => AlertDialog(
- content: Text(AppLocalizations.of(context)!.study_selection_single_reason),
+ content: Text(
+ AppLocalizations.of(context)!
+ .study_selection_single_reason,
+ ),
),
),
- )
+ ),
],
),
),
],
),
),
- _hiddenStudies
- ? Column(
- children: [
- MaterialBanner(
- padding: const EdgeInsets.all(8),
- leading: Icon(
- MdiIcons.exclamationThick,
- color: Colors.orange,
- size: 32,
- ),
- content: Text(
- AppLocalizations.of(context)!.study_selection_hidden_studies,
- style: Theme.of(context).textTheme.titleSmall,
- ),
- actions: const [SizedBox.shrink()],
- backgroundColor: Colors.yellow[100],
- ),
- const SizedBox(height: 16),
- ],
- )
- : const SizedBox.shrink(),
+ if (_hiddenStudies)
+ Column(
+ children: [
+ MaterialBanner(
+ padding: const EdgeInsets.all(8),
+ leading: Icon(
+ MdiIcons.exclamationThick,
+ color: Colors.orange,
+ size: 32,
+ ),
+ content: Text(
+ AppLocalizations.of(context)!
+ .study_selection_hidden_studies,
+ style: Theme.of(context).textTheme.titleSmall,
+ ),
+ actions: const [SizedBox.shrink()],
+ backgroundColor: Colors.yellow[100],
+ ),
+ const SizedBox(height: 16),
+ ],
+ )
+ else
+ const SizedBox.shrink(),
Expanded(
child: RetryFutureBuilder>(
tryFunction: () async => publishedStudies,
- successBuilder: (BuildContext context, ExtractionResult? extractionResult) {
+ successBuilder: (
+ BuildContext context,
+ ExtractionResult? extractionResult,
+ ) {
final studies = extractionResult!.extracted;
if (extractionResult is ExtractionFailedException) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_hiddenStudies) return;
- debugPrint('${extractionResult.notExtracted.length} studies could not be extracted.');
+ debugPrint(
+ '${extractionResult.notExtracted.length} studies could not be extracted.',
+ );
setState(() {
_hiddenStudies = true;
});
@@ -158,7 +171,10 @@ class _StudySelectionScreenState extends State {
child: OutlinedButton.icon(
icon: Icon(MdiIcons.key),
onPressed: () async {
- await showDialog(context: context, builder: (_) => const InviteCodeDialog());
+ await showDialog(
+ context: context,
+ builder: (_) => const InviteCodeDialog(),
+ );
},
label: Text(AppLocalizations.of(context)!.invite_code_button),
),
@@ -198,7 +214,9 @@ class _InviteCodeDialogState extends State {
controller: _controller,
validator: (_) => _errorMessage,
autovalidateMode: AutovalidateMode.always,
- decoration: InputDecoration(labelText: AppLocalizations.of(context)!.invite_code),
+ decoration: InputDecoration(
+ labelText: AppLocalizations.of(context)!.invite_code,
+ ),
),
actions: [
OutlinedButton.icon(
@@ -223,7 +241,8 @@ class _InviteCodeDialogState extends State {
if (result == null) {
setState(() {
- _errorMessage = AppLocalizations.of(context)!.invalid_invite_code;
+ _errorMessage =
+ AppLocalizations.of(context)!.invalid_invite_code;
});
} else {
setState(() {
@@ -232,10 +251,10 @@ class _InviteCodeDialogState extends State {
Map? studyResult;
try {
- studyResult = await (Supabase.instance.client.rpc(
+ studyResult = await Supabase.instance.client.rpc(
'get_study_record_from_invite',
params: {'invite_code': _controller.text},
- ).single());
+ ).single();
} on PostgrestException catch (error) {
print(error.message);
setState(() {
@@ -281,7 +300,7 @@ class _InviteCodeDialogState extends State {
}
}
},
- )
+ ),
],
);
}
diff --git a/app/lib/screens/study/report/disclaimer_section.dart b/app/lib/screens/study/report/disclaimer_section.dart
index 16950dfe3..f13956207 100644
--- a/app/lib/screens/study/report/disclaimer_section.dart
+++ b/app/lib/screens/study/report/disclaimer_section.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'generic_section.dart';
+import 'package:studyu_app/screens/study/report/generic_section.dart';
class DisclaimerSection extends GenericSection {
const DisclaimerSection(super.subject, {super.key, super.onTap});
diff --git a/app/lib/screens/study/report/general_details_section.dart b/app/lib/screens/study/report/general_details_section.dart
index cb9bd8ab9..6bc44226f 100644
--- a/app/lib/screens/study/report/general_details_section.dart
+++ b/app/lib/screens/study/report/general_details_section.dart
@@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
-
-import '../../../widgets/study_tile.dart';
-import 'generic_section.dart';
+import 'package:studyu_app/screens/study/report/generic_section.dart';
+import 'package:studyu_app/widgets/study_tile.dart';
class GeneralDetailsSection extends GenericSection {
const GeneralDetailsSection(super.subject, {super.key, super.onTap});
diff --git a/app/lib/screens/study/report/performance/performance_details.dart b/app/lib/screens/study/report/performance/performance_details.dart
index ed9c71fe8..28b5210ca 100644
--- a/app/lib/screens/study/report/performance/performance_details.dart
+++ b/app/lib/screens/study/report/performance/performance_details.dart
@@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/widgets/intervention_card.dart';
import 'package:studyu_core/core.dart';
-import '../../../../routes.dart';
-import '../../../../widgets/intervention_card.dart';
-
class PerformanceDetailsScreen extends StatelessWidget {
final StudySubject? reportSubject;
- static MaterialPageRoute routeFor({required StudySubject? subject}) => MaterialPageRoute(
+ static MaterialPageRoute routeFor({required StudySubject? subject}) =>
+ MaterialPageRoute(
builder: (_) => PerformanceDetailsScreen(subject),
settings: const RouteSettings(name: Routes.performanceDetails),
);
@@ -18,8 +18,9 @@ class PerformanceDetailsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
- final interventions =
- reportSubject!.selectedInterventions.where((intervention) => !intervention.isBaseline()).toList();
+ final interventions = reportSubject!.selectedInterventions
+ .where((intervention) => !intervention.isBaseline())
+ .toList();
return Scaffold(
appBar: AppBar(
@@ -35,15 +36,20 @@ class PerformanceDetailsScreen extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.all(8),
- child: Text(AppLocalizations.of(context)!.performance_overview, style: theme.textTheme.titleMedium),
+ child: Text(
+ AppLocalizations.of(context)!.performance_overview,
+ style: theme.textTheme.titleMedium,
+ ),
),
Padding(
padding: const EdgeInsets.all(8),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
- AppLocalizations.of(context)!.performance_overview_interventions,
- style: theme.textTheme.titleLarge!.copyWith(color: theme.primaryColor),
+ AppLocalizations.of(context)!
+ .performance_overview_interventions,
+ style: theme.textTheme.titleLarge!
+ .copyWith(color: theme.primaryColor),
),
),
),
@@ -51,16 +57,20 @@ class PerformanceDetailsScreen extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: interventions.length,
- itemBuilder: (context, index) =>
- InterventionPerformanceBar(subject: reportSubject, intervention: interventions[index]),
+ itemBuilder: (context, index) => InterventionPerformanceBar(
+ subject: reportSubject,
+ intervention: interventions[index],
+ ),
),
Padding(
padding: const EdgeInsets.all(8),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
- AppLocalizations.of(context)!.performance_overview_observations,
- style: theme.textTheme.titleLarge!.copyWith(color: theme.primaryColor),
+ AppLocalizations.of(context)!
+ .performance_overview_observations,
+ style: theme.textTheme.titleLarge!
+ .copyWith(color: theme.primaryColor),
),
),
),
@@ -86,7 +96,11 @@ class InterventionPerformanceBar extends StatelessWidget {
final Intervention intervention;
final StudySubject? subject;
- const InterventionPerformanceBar({required this.intervention, required this.subject, super.key});
+ const InterventionPerformanceBar({
+ required this.intervention,
+ required this.subject,
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -95,7 +109,11 @@ class InterventionPerformanceBar extends StatelessWidget {
padding: const EdgeInsets.all(16),
child: Column(
children: [
- InterventionCard(intervention, showTasks: false, showDescription: false),
+ InterventionCard(
+ intervention,
+ showTasks: false,
+ showDescription: false,
+ ),
const SizedBox(height: 8),
...intervention.tasks.map(
(task) => PerformanceBar(
@@ -103,7 +121,7 @@ class InterventionPerformanceBar extends StatelessWidget {
completed: subject!.completedTasksFor(task),
total: subject!.totalTaskCountFor(task),
),
- )
+ ),
],
),
),
@@ -115,7 +133,11 @@ class ObservationPerformanceBar extends StatelessWidget {
final Observation observation;
final StudySubject? subject;
- const ObservationPerformanceBar({required this.observation, required this.subject, super.key});
+ const ObservationPerformanceBar({
+ required this.observation,
+ required this.subject,
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -137,7 +159,12 @@ class PerformanceBar extends StatelessWidget {
final int completed;
final int total;
- const PerformanceBar({required this.task, required this.completed, required this.total, super.key});
+ const PerformanceBar({
+ required this.task,
+ required this.completed,
+ required this.total,
+ super.key,
+ });
@override
Widget build(BuildContext context) {
@@ -163,9 +190,9 @@ class PerformanceBar extends StatelessWidget {
'${(completed / total * 100).toStringAsFixed(2).replaceAll('.00', '')} %',
style: const TextStyle(fontWeight: FontWeight.bold),
),
- )
+ ),
],
- )
+ ),
],
);
}
diff --git a/app/lib/screens/study/report/performance/performance_section.dart b/app/lib/screens/study/report/performance/performance_section.dart
index 41dec298b..da07b1105 100644
--- a/app/lib/screens/study/report/performance/performance_section.dart
+++ b/app/lib/screens/study/report/performance/performance_section.dart
@@ -3,10 +3,9 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:rainbow_color/rainbow_color.dart';
+import 'package:studyu_app/screens/study/report/generic_section.dart';
import 'package:studyu_core/core.dart';
-import '../generic_section.dart';
-
class PerformanceSection extends GenericSection {
const PerformanceSection(super.subject, {super.key, super.onTap});
@@ -17,13 +16,19 @@ class PerformanceSection extends GenericSection {
@override
Widget buildContent(BuildContext context) {
- final interventions =
- subject!.selectedInterventions.where((intervention) => intervention.id != Study.baselineID).toList();
+ final interventions = subject!.selectedInterventions
+ .where((intervention) => intervention.id != Study.baselineID)
+ .toList();
final interventionProgress = interventions.map((intervention) {
- final countableInterventions = getCountableObservationAmount(intervention);
- return min(countableInterventions == 0 ? 0 : countableInterventions / maximum, 1);
+ final countableInterventions =
+ getCountableObservationAmount(intervention);
+ return min(
+ countableInterventions == 0 ? 0 : countableInterventions / maximum,
+ 1,
+ );
}).toList();
- return interventions.length != 2 || subject!.study.reportSpecification.primary == null
+ return interventions.length != 2 ||
+ subject!.study.reportSpecification.primary == null
? Center(
child: Text(AppLocalizations.of(context)!.performance),
)
@@ -64,7 +69,10 @@ class PerformanceSection extends GenericSection {
);
}
- String getPowerLevelDescription(BuildContext context, List interventionProgress) {
+ String getPowerLevelDescription(
+ BuildContext context,
+ List interventionProgress,
+ ) {
if (interventionProgress.any((progress) => progress < minimumRatio)) {
return AppLocalizations.of(context)!.not_enough_data;
} else if (interventionProgress.any((progress) => progress < 1)) {
@@ -81,13 +89,23 @@ class PerformanceSection extends GenericSection {
}
var countable = 0;
- subject!.getResultsByDate(interventionId: intervention.id).values.forEach((progress) {
+ subject!
+ .getResultsByDate(interventionId: intervention.id)
+ .values
+ .forEach((progress) {
if (progress
- .where((result) => intervention.tasks.any((interventionTask) => interventionTask.id == result.taskId))
+ .where(
+ (result) => intervention.tasks.any(
+ (interventionTask) => interventionTask.id == result.taskId,
+ ),
+ )
.length ==
interventionsPerDay) {
countable += progress
- .where((result) => subject!.study.observations.any((observation) => observation.id == result.taskId))
+ .where(
+ (result) => subject!.study.observations
+ .any((observation) => observation.id == result.taskId),
+ )
.length;
}
});
@@ -126,12 +144,18 @@ class PerformanceBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final rainbow = Rainbow(spectrum: [Colors.red, Colors.yellow, Colors.green], rangeStart: 0, rangeEnd: 1);
+ final rainbow = Rainbow(
+ spectrum: [Colors.red, Colors.yellow, Colors.green],
+ rangeStart: 0,
+ rangeEnd: 1,
+ );
final fullSpectrum = List.generate(3, (index) => index * 0.5)
.map((index) => rainbow[index].withOpacity(0.4))
.toList();
final colorSamples =
- List.generate(11, (index) => index * 0.1 * progress).map((index) => rainbow[index]).toList();
+ List.generate(11, (index) => index * 0.1 * progress)
+ .map((index) => rainbow[index])
+ .toList();
final spacing = (minimum! * 1000).floor();
@@ -172,7 +196,7 @@ class PerformanceBar extends StatelessWidget {
Container(
width: 2,
color: Colors.grey[600],
- )
+ ),
],
),
],
diff --git a/app/lib/screens/study/report/report_details.dart b/app/lib/screens/study/report/report_details.dart
index 5ae98fde1..32bf3b03e 100644
--- a/app/lib/screens/study/report/report_details.dart
+++ b/app/lib/screens/study/report/report_details.dart
@@ -1,19 +1,19 @@
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:studyu_app/routes.dart';
+import 'package:studyu_app/screens/study/report/disclaimer_section.dart';
+import 'package:studyu_app/screens/study/report/general_details_section.dart';
+import 'package:studyu_app/screens/study/report/performance/performance_details.dart';
+import 'package:studyu_app/screens/study/report/performance/performance_section.dart';
+import 'package:studyu_app/screens/study/report/report_section_container.dart';
import 'package:studyu_core/core.dart';
-import '../../../routes.dart';
-import 'disclaimer_section.dart';
-import 'general_details_section.dart';
-import 'performance/performance_details.dart';
-import 'performance/performance_section.dart';
-import 'report_section_container.dart';
-
class ReportDetailsScreen extends StatelessWidget {
final StudySubject subject;
- static MaterialPageRoute routeFor({required StudySubject subject}) => MaterialPageRoute(
+ static MaterialPageRoute routeFor({required StudySubject subject}) =>
+ MaterialPageRoute(
builder: (_) => ReportDetailsScreen(subject),
settings: const RouteSettings(name: Routes.reportDetails),
);
@@ -43,17 +43,23 @@ class ReportDetailsScreen extends StatelessWidget {
DisclaimerSection(subject),
PerformanceSection(
subject,
- onTap: () => Navigator.push(context, PerformanceDetailsScreen.routeFor(subject: subject)),
+ onTap: () => Navigator.push(
+ context,
+ PerformanceDetailsScreen.routeFor(subject: subject),
+ ),
),
- if (subject.study.reportSpecification.primary != null && (subject.completedStudy || kDebugMode))
+ if (subject.study.reportSpecification.primary != null &&
+ (subject.completedStudy || kDebugMode))
ReportSectionContainer(
subject.study.reportSpecification.primary!,
subject: subject,
primary: true,
),
- if (subject.study.reportSpecification.secondary.isNotEmpty && (subject.completedStudy || kDebugMode))
- ...subject.study.reportSpecification.secondary
- .map((section) => ReportSectionContainer(section, subject: subject))
+ if (subject.study.reportSpecification.secondary.isNotEmpty &&
+ (subject.completedStudy || kDebugMode))
+ ...subject.study.reportSpecification.secondary.map(
+ (section) => ReportSectionContainer(section, subject: subject),
+ ),
],
),
),
diff --git a/app/lib/screens/study/report/report_history.dart b/app/lib/screens/study/report/report_history.dart
index 7fcd689e7..2250a7346 100644
--- a/app/lib/screens/study/report/report_history.dart
+++ b/app/lib/screens/study/report/report_history.dart
@@ -2,13 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:provider/provider.dart';
+import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/screens/study/report/report_details.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
-import '../../../models/app_state.dart';
-import 'report_details.dart';
-
class ReportHistoryScreen extends StatelessWidget {
const ReportHistoryScreen({super.key});
@@ -21,8 +20,11 @@ class ReportHistoryScreen extends StatelessWidget {
),
),
body: RetryFutureBuilder>(
- tryFunction: () => StudySubject.getStudyHistory(Supabase.instance.client.auth.currentUser!.id),
- successBuilder: (BuildContext context, List? pastStudies) {
+ tryFunction: () => StudySubject.getStudyHistory(
+ Supabase.instance.client.auth.currentUser!.id,
+ ),
+ successBuilder:
+ (BuildContext context, List? pastStudies) {
return ListView.builder(
itemCount: pastStudies!.length,
itemBuilder: (context, index) {
@@ -49,7 +51,10 @@ class ReportHistoryItem extends StatelessWidget {
color: isActiveStudy ? Colors.green[600] : theme.cardColor,
child: InkWell(
onTap: () {
- Navigator.push(context, ReportDetailsScreen.routeFor(subject: subject));
+ Navigator.push(
+ context,
+ ReportDetailsScreen.routeFor(subject: subject),
+ );
},
child: Padding(
padding: const EdgeInsets.all(20),
@@ -58,14 +63,17 @@ class ReportHistoryItem extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Icon(
- MdiIcons.fromString(subject.study.iconName) ?? MdiIcons.accountHeart,
+ MdiIcons.fromString(subject.study.iconName) ??
+ MdiIcons.accountHeart,
color: isActiveStudy ? Colors.white : Colors.black,
),
const SizedBox(width: 16),
Expanded(
child: Text(
subject.study.title!,
- style: theme.textTheme.headlineSmall!.copyWith(color: isActiveStudy ? Colors.white : Colors.black),
+ style: theme.textTheme.headlineSmall!.copyWith(
+ color: isActiveStudy ? Colors.white : Colors.black,
+ ),
),
),
],
diff --git a/app/lib/screens/study/report/report_section_container.dart b/app/lib/screens/study/report/report_section_container.dart
index ef8bfb5ca..df4539b35 100644
--- a/app/lib/screens/study/report/report_section_container.dart
+++ b/app/lib/screens/study/report/report_section_container.dart
@@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:studyu_app/screens/study/report/report_section_widget.dart';
import 'package:studyu_app/screens/study/report/sections/average_section_widget.dart';
import 'package:studyu_app/screens/study/report/sections/linear_regression_section_widget.dart';
import 'package:studyu_core/core.dart';
-import 'report_section_widget.dart';
-
-typedef SectionBuilder = ReportSectionWidget Function(ReportSection section, StudySubject subject);
+typedef SectionBuilder = ReportSectionWidget Function(
+ ReportSection section,
+ StudySubject subject,
+);
class ReportSectionContainer extends StatelessWidget {
final ReportSection section;
@@ -14,11 +16,18 @@ class ReportSectionContainer extends StatelessWidget {
final bool primary;
final GestureTapCallback? onTap;
- const ReportSectionContainer(this.section, {super.key, required this.subject, this.onTap, this.primary = false});
+ const ReportSectionContainer(
+ this.section, {
+ super.key,
+ required this.subject,
+ this.onTap,
+ this.primary = false,
+ });
ReportSectionWidget buildContents(BuildContext context) => switch (section) {
- AverageSection averageSection => AverageSectionWidget(subject, averageSection),
- LinearRegressionSection linearRegressionSection =>
+ final AverageSection averageSection =>
+ AverageSectionWidget(subject, averageSection),
+ final LinearRegressionSection linearRegressionSection =>
LinearRegressionSectionWidget(subject, linearRegressionSection),
_ => throw ArgumentError('Section type ${section.type} not supported.'),
};
@@ -26,7 +35,8 @@ class ReportSectionContainer extends StatelessWidget {
List buildPrimaryHeader(BuildContext context, ThemeData theme) => [
Text(
AppLocalizations.of(context)!.report_primary_result.toUpperCase(),
- style: theme.textTheme.labelSmall!.copyWith(color: theme.colorScheme.secondary),
+ style: theme.textTheme.labelSmall!
+ .copyWith(color: theme.colorScheme.secondary),
),
const SizedBox(height: 4),
];
diff --git a/app/lib/screens/study/report/sections/average_section_widget.dart b/app/lib/screens/study/report/sections/average_section_widget.dart
index ad68f675f..bf949f7d7 100644
--- a/app/lib/screens/study/report/sections/average_section_widget.dart
+++ b/app/lib/screens/study/report/sections/average_section_widget.dart
@@ -1,14 +1,13 @@
import 'package:collection/collection.dart';
import 'package:fl_chart/fl_chart.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:studyu_app/screens/study/report/report_section_widget.dart';
+import 'package:studyu_app/screens/study/report/util/plot_utilities.dart';
import 'package:studyu_app/theme.dart';
+import 'package:studyu_app/util/data_processing.dart';
import 'package:studyu_core/core.dart';
-import '../../../../util/data_processing.dart';
-import '../report_section_widget.dart';
-import '../util/plot_utilities.dart';
-
class AverageSectionWidget extends ReportSectionWidget {
final AverageSection section;
@@ -17,13 +16,21 @@ class AverageSectionWidget extends ReportSectionWidget {
@override
Widget build(BuildContext context) {
final data = getAggregatedData().toList();
- final taskTitle =
- subject.study.observations.firstWhereOrNull((element) => element.id == section.resultProperty!.task)?.title;
+ final taskTitle = subject.study.observations
+ .firstWhereOrNull(
+ (element) => element.id == section.resultProperty!.task,
+ )
+ ?.title;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
- if (taskTitle != null) Text(taskTitle, style: theme.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.bold)),
+ if (taskTitle != null)
+ Text(
+ taskTitle,
+ style: theme.textTheme.bodyLarge!
+ .copyWith(fontWeight: FontWeight.bold),
+ ),
const SizedBox(height: 8),
getLegend(context, data),
const SizedBox(height: 8),
@@ -36,21 +43,27 @@ class AverageSectionWidget extends ReportSectionWidget {
final numberOfPhases = subject.interventionOrder.length;
final phaseDuration = subject.study.schedule.phaseDuration;
return Iterable.generate(numberOfPhases)
- .map((i) => (((i + 1) * phaseDuration - ((phaseDuration / 2) - 1)) - 1).floor())
+ .map(
+ (i) => (((i + 1) * phaseDuration - ((phaseDuration / 2) - 1)) - 1)
+ .floor(),
+ )
.toList();
}
List get phasePos {
final numberOfPhases = subject.interventionOrder.length;
final phaseDuration = subject.study.schedule.phaseDuration;
- return Iterable.generate(numberOfPhases).map((i) => (i + 1) * phaseDuration).toList();
+ return Iterable.generate(numberOfPhases)
+ .map((i) => (i + 1) * phaseDuration)
+ .toList();
}
Widget getLegend(BuildContext context, List data) {
final interventionNames = getInterventionNames(context);
final legends = {
- for (var entry in data)
- interventionNames[entry.intervention]!: Legend(interventionNames[entry.intervention]!, getColor(entry))
+ for (final entry in data)
+ interventionNames[entry.intervention]!:
+ Legend(interventionNames[entry.intervention]!, getColor(entry)),
};
return LegendsListWidget(legends: legends.values.toList());
}
@@ -58,27 +71,30 @@ class AverageSectionWidget extends ReportSectionWidget {
Widget getDiagram(BuildContext context, List data) {
return BarChart(
getChartData(context, data),
- swapAnimationDuration: const Duration(milliseconds: 150), // Optional
- swapAnimationCurve: Curves.linear, // Optional
);
}
BarChartData getChartData(BuildContext context, List data) {
final barGroups = getBarGroups(context, data);
- final maxY = ((data.sortedBy((entry) => entry.value).toList().lastOrNull?.value ?? 0) * 1.1).ceilToDouble();
+ final maxY =
+ ((data.sortedBy((entry) => entry.value).toList().lastOrNull?.value ??
+ 0) *
+ 1.1)
+ .ceilToDouble();
return BarChartData(
titlesData: FlTitlesData(
- bottomTitles: AxisTitles(
- axisNameWidget:
- (section.aggregate != TemporalAggregation.intervention) ? const Text("Phase") : const Text(""),
- sideTitles: SideTitles(
- showTitles: true,
- getTitlesWidget: getTitles,
- )),
- topTitles: const AxisTitles(
- sideTitles: SideTitles(
- showTitles: false,
- ))),
+ bottomTitles: AxisTitles(
+ axisNameWidget:
+ (section.aggregate != TemporalAggregation.intervention)
+ ? const Text("Phase")
+ : const Text(""),
+ sideTitles: SideTitles(
+ showTitles: true,
+ getTitlesWidget: getTitles,
+ ),
+ ),
+ topTitles: const AxisTitles(),
+ ),
gridData: getGridData(barGroups),
alignment: BarChartAlignment.spaceAround,
barGroups: barGroups,
@@ -112,7 +128,10 @@ class AverageSectionWidget extends ReportSectionWidget {
}
}
- List getBarGroups(BuildContext context, List data) {
+ List getBarGroups(
+ BuildContext context,
+ List data,
+ ) {
if (data.isEmpty) return [BarChartGroupData(x: 0)];
int barCount = 0;
@@ -122,7 +141,8 @@ class AverageSectionWidget extends ReportSectionWidget {
case TemporalAggregation.phase:
barCount = subject.interventionOrder.length;
case TemporalAggregation.intervention:
- barCount = subject.selectedInterventionIds.length + (subject.study.schedule.includeBaseline ? 1 : 0);
+ barCount = subject.selectedInterventionIds.length +
+ (subject.study.schedule.includeBaseline ? 1 : 0);
default:
}
@@ -142,9 +162,13 @@ class AverageSectionWidget extends ReportSectionWidget {
return BarChartGroupData(x: index, barsSpace: 0, barRods: [rod]);
}
- var starter = List.generate(barCount, barGenerator);
- for (var entry in data) {
- starter[entry.x.round()] = barGenerator(entry.x.round(), y: entry.value.toDouble(), color: getColor(entry));
+ final starter = List.generate(barCount, barGenerator);
+ for (final entry in data) {
+ starter[entry.x.round()] = barGenerator(
+ entry.x.round(),
+ y: entry.value.toDouble(),
+ color: getColor(entry),
+ );
}
return starter;
}
@@ -164,12 +188,13 @@ class AverageSectionWidget extends ReportSectionWidget {
final lineCount = barGroups.length * 2;
bool drawLine(double val) {
// draw when we are at the border between two phases
- return (val * lineCount % (2 * subject.study.schedule.phaseDuration)).toInt() == 0;
+ return (val * lineCount % (2 * subject.study.schedule.phaseDuration))
+ .toInt() ==
+ 0;
}
return FlGridData(
drawHorizontalLine: false,
- drawVerticalLine: true,
checkToShowVerticalLine: drawLine,
verticalInterval: 1 / lineCount,
);
@@ -183,23 +208,31 @@ class AverageSectionWidget extends ReportSectionWidget {
switch (section.aggregate) {
case TemporalAggregation.day:
//c = colors[subject.interventionOrder.indexOf(diagram.intervention)];
- if (subject.study.schedule.includeBaseline && diagram.x < subject.study.schedule.phaseDuration) {
+ if (subject.study.schedule.includeBaseline &&
+ diagram.x < subject.study.schedule.phaseDuration) {
// if id == "_baseline"
c = baselineColor;
} else {
- c = colors[subject.selectedInterventions.map((e) => e.id).toList().indexOf(diagram.intervention)];
+ c = colors[subject.selectedInterventions
+ .map((e) => e.id)
+ .toList()
+ .indexOf(diagram.intervention)];
}
case TemporalAggregation.phase:
if (subject.study.schedule.includeBaseline && diagram.x == 0) {
c = baselineColor;
} else {
- c = colors[subject.selectedInterventions.map((e) => e.id).toList().indexOf(diagram.intervention)];
+ c = colors[subject.selectedInterventions
+ .map((e) => e.id)
+ .toList()
+ .indexOf(diagram.intervention)];
}
case TemporalAggregation.intervention:
if (subject.study.schedule.includeBaseline && diagram.x == 0) {
c = baselineColor;
} else {
- c = colors[diagram.x.round() - (subject.study.schedule.includeBaseline ? 1 : 0)];
+ c = colors[diagram.x.round() -
+ (subject.study.schedule.includeBaseline ? 1 : 0)];
}
default:
}
@@ -207,7 +240,9 @@ class AverageSectionWidget extends ReportSectionWidget {
}
int getDayIndex(DateTime key) {
- if (subject.study.schedule.includeBaseline) return subject.getDayOfStudyFor(key);
+ if (subject.study.schedule.includeBaseline) {
+ return subject.getDayOfStudyFor(key);
+ }
final schedule = subject.scheduleFor(subject.startedAt!);
// this always has to be found because studies have to have at least 2
// interventions
@@ -256,7 +291,7 @@ class AverageSectionWidget extends ReportSectionWidget {
.groupBy((e) => e.intervention)
.aggregateWithKey(
(data, intervention) => DiagramDatum(
- order[intervention] as num,
+ order[intervention]! as num,
foldAggregateMean()(data.map((e) => e.value)),
null,
intervention,
@@ -267,7 +302,10 @@ class AverageSectionWidget extends ReportSectionWidget {
}
Map getInterventionNames(BuildContext context) {
- final names = {for (var intervention in subject.study.interventions) intervention.id: intervention.name};
+ final names = {
+ for (final intervention in subject.study.interventions)
+ intervention.id: intervention.name,
+ };
names[Study.baselineID] = AppLocalizations.of(context)!.baseline;
return names;
}
diff --git a/app/lib/screens/study/report/util/plot_utilities.dart b/app/lib/screens/study/report/util/plot_utilities.dart
index eddb3cad0..34294da77 100644
--- a/app/lib/screens/study/report/util/plot_utilities.dart
+++ b/app/lib/screens/study/report/util/plot_utilities.dart
@@ -3,7 +3,7 @@ import 'package:studyu_core/core.dart';
Map getInterventionPositions(List interventions) {
final order = {};
- for (var intervention in interventions) {
+ for (final intervention in interventions) {
if (!order.containsKey(intervention.id)) {
order[intervention.id] = order.length;
}
diff --git a/app/lib/screens/study/tasks/intervention/checkmark_task_widget.dart b/app/lib/screens/study/tasks/intervention/checkmark_task_widget.dart
index 747cd1430..a363ca969 100644
--- a/app/lib/screens/study/tasks/intervention/checkmark_task_widget.dart
+++ b/app/lib/screens/study/tasks/intervention/checkmark_task_widget.dart
@@ -18,27 +18,38 @@ class CheckmarkTaskWidget extends StatefulWidget {
}
class _CheckmarkTaskWidgetState extends State {
- DateTime? loginClickTime;
+ DateTime _lastClickTime = DateTime.now();
bool _isLoading = false;
@override
Widget build(BuildContext context) {
return ElevatedButton.icon(
style: ButtonStyle(
- backgroundColor: MaterialStateProperty.all(Colors.green),
- textStyle: MaterialStateProperty.all(const TextStyle(color: Colors.white))),
+ backgroundColor: WidgetStateProperty.all(Colors.green),
+ textStyle: WidgetStateProperty.all(
+ const TextStyle(color: Colors.white),
+ ),
+ ),
onPressed: () async {
- if (isRedundantClick(loginClickTime)) return;
+ if (isRedundantClick(_lastClickTime)) return;
setState(() {
_isLoading = true;
+ _lastClickTime = DateTime.now();
});
await handleTaskCompletion(context, (StudySubject? subject) async {
try {
- await subject!
- .addResult(taskId: widget.task!.id, periodId: widget.completionPeriod!.id, result: true);
+ await subject!.addResult(
+ taskId: widget.task!.id,
+ periodId: widget.completionPeriod!.id,
+ result: true,
+ );
} on SocketException catch (_) {
await subject!.addResult(
- taskId: widget.task!.id, periodId: widget.completionPeriod!.id, result: true, offline: true);
+ taskId: widget.task!.id,
+ periodId: widget.completionPeriod!.id,
+ result: true,
+ offline: true,
+ );
rethrow;
}
});
@@ -48,7 +59,9 @@ class _CheckmarkTaskWidgetState extends State {
if (!context.mounted) return;
Navigator.pop(context, true);
},
- icon: _isLoading ? const CircularProgressIndicator(color: Colors.white) : const Icon(Icons.check),
+ icon: _isLoading
+ ? const CircularProgressIndicator(color: Colors.white)
+ : const Icon(Icons.check),
label: Text(AppLocalizations.of(context)!.complete),
);
}
diff --git a/app/lib/screens/study/tasks/observation/questionnaire_task_widget.dart b/app/lib/screens/study/tasks/observation/questionnaire_task_widget.dart
index e53427ce6..fe551f9d5 100644
--- a/app/lib/screens/study/tasks/observation/questionnaire_task_widget.dart
+++ b/app/lib/screens/study/tasks/observation/questionnaire_task_widget.dart
@@ -6,33 +6,49 @@ import 'package:studyu_app/screens/study/tasks/task_screen.dart';
import 'package:studyu_app/util/misc.dart';
import 'package:studyu_app/util/study_subject_extension.dart';
import 'package:studyu_app/util/temporary_storage_handler.dart';
-import 'package:studyu_core/core.dart';
import 'package:studyu_app/widgets/questionnaire/questionnaire_widget.dart';
+import 'package:studyu_core/core.dart';
class QuestionnaireTaskWidget extends StatefulWidget {
final QuestionnaireTask task;
final CompletionPeriod completionPeriod;
- const QuestionnaireTaskWidget({required this.task, required this.completionPeriod, super.key});
+ const QuestionnaireTaskWidget({
+ required this.task,
+ required this.completionPeriod,
+ super.key,
+ });
@override
- State createState() => _QuestionnaireTaskWidgetState();
+ State createState() =>
+ _QuestionnaireTaskWidgetState();
}
class _QuestionnaireTaskWidgetState extends State {
dynamic response;
late bool responseValidator;
- DateTime? loginClickTime;
+ DateTime _lastClickTime = DateTime.now();
bool _isLoading = false;
final GlobalKey formKey = GlobalKey();
- Future _addQuestionnaireResult(T response, BuildContext context) async {
+ Future _addQuestionnaireResult(
+ T response,
+ BuildContext context,
+ ) async {
await handleTaskCompletion(context, (StudySubject? subject) async {
try {
- await subject!.addResult(taskId: widget.task.id, periodId: widget.completionPeriod.id, result: response);
+ await subject!.addResult(
+ taskId: widget.task.id,
+ periodId: widget.completionPeriod.id,
+ result: response,
+ );
} on SocketException catch (_) {
await subject!.addResult(
- taskId: widget.task.id, periodId: widget.completionPeriod.id, result: response, offline: true);
+ taskId: widget.task.id,
+ periodId: widget.completionPeriod.id,
+ result: response,
+ offline: true,
+ );
rethrow;
}
});
@@ -64,32 +80,40 @@ class _QuestionnaireTaskWidgetState extends State {
),
),
),
- response != null && responseValidator
- ? ElevatedButton.icon(
- style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Colors.green)),
- onPressed: () async {
- if (isRedundantClick(loginClickTime)) {
- return;
- }
- if (!formKey.currentState!.validate()) {
- return;
- }
- setState(() {
- _isLoading = true;
- });
- switch (response) {
- case QuestionnaireState questionnaireState:
- await _addQuestionnaireResult(questionnaireState, context);
- break;
- }
- setState(() {
- _isLoading = false;
- });
- },
- icon: _isLoading ? const CircularProgressIndicator(color: Colors.white) : const Icon(Icons.check),
- label: Text(AppLocalizations.of(context)!.complete),
- )
- : const SizedBox.shrink(),
+ if (response != null && responseValidator)
+ ElevatedButton.icon(
+ style: ButtonStyle(
+ backgroundColor: WidgetStateProperty.all(Colors.green),
+ ),
+ onPressed: () async {
+ if (isRedundantClick(_lastClickTime)) {
+ return;
+ }
+ if (!formKey.currentState!.validate()) {
+ return;
+ }
+ setState(() {
+ _isLoading = true;
+ _lastClickTime = DateTime.now();
+ });
+ switch (response) {
+ case final QuestionnaireState questionnaireState:
+ await _addQuestionnaireResult(
+ questionnaireState,
+ context,
+ );
+ }
+ setState(() {
+ _isLoading = false;
+ });
+ },
+ icon: _isLoading
+ ? const CircularProgressIndicator(color: Colors.white)
+ : const Icon(Icons.check),
+ label: Text(AppLocalizations.of(context)!.complete),
+ )
+ else
+ const SizedBox.shrink(),
],
);
}
diff --git a/app/lib/screens/study/tasks/task_screen.dart b/app/lib/screens/study/tasks/task_screen.dart
index 04a66b34d..53301b424 100644
--- a/app/lib/screens/study/tasks/task_screen.dart
+++ b/app/lib/screens/study/tasks/task_screen.dart
@@ -1,19 +1,22 @@
import 'dart:io';
import 'package:flutter/material.dart';
+import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/screens/study/tasks/intervention/checkmark_task_widget.dart';
+import 'package:studyu_app/screens/study/tasks/observation/questionnaire_task_widget.dart';
import 'package:studyu_app/util/cache.dart';
import 'package:studyu_app/widgets/html_text.dart';
import 'package:studyu_core/core.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
-import 'intervention/checkmark_task_widget.dart';
-import 'observation/questionnaire_task_widget.dart';
class TaskScreen extends StatefulWidget {
final TaskInstance taskInstance;
- static MaterialPageRoute routeFor({required TaskInstance taskInstance}) => MaterialPageRoute(
+ static MaterialPageRoute routeFor({
+ required TaskInstance taskInstance,
+ }) =>
+ MaterialPageRoute(
builder: (_) => TaskScreen(taskInstance: taskInstance),
);
@@ -31,17 +34,19 @@ class _TaskScreenState extends State {
void didChangeDependencies() {
super.didChangeDependencies();
subject = context.watch().activeSubject;
- taskInstance = TaskInstance.fromInstanceId(widget.taskInstance.id, study: subject!.study);
+ taskInstance = TaskInstance.fromInstanceId(
+ widget.taskInstance.id,
+ study: subject!.study,
+ );
}
Widget _buildTask() {
switch (taskInstance.task) {
- case CheckmarkTask checkmarkTask:
+ case final CheckmarkTask checkmarkTask:
return SingleChildScrollView(
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
children: [
HtmlText(taskInstance.task.header, centered: true),
const SizedBox(height: 20),
@@ -49,12 +54,12 @@ class _TaskScreenState extends State {
task: checkmarkTask,
key: UniqueKey(),
completionPeriod: taskInstance.completionPeriod,
- )
+ ),
],
),
),
);
- case QuestionnaireTask questionnaireTask:
+ case final QuestionnaireTask questionnaireTask:
return QuestionnaireTaskWidget(
task: questionnaireTask,
key: UniqueKey(),
@@ -77,7 +82,10 @@ class _TaskScreenState extends State {
}
}
-handleTaskCompletion(BuildContext context, Function(StudySubject?) completionCallback) async {
+Future handleTaskCompletion(
+ BuildContext context,
+ Function(StudySubject?) completionCallback,
+) async {
final state = context.read();
final activeSubject = state.activeSubject;
try {
@@ -93,7 +101,10 @@ handleTaskCompletion(BuildContext context, Function(StudySubject?) completionCal
SnackBar(
content: Text(AppLocalizations.of(context)!.could_not_save_results),
duration: const Duration(seconds: 10),
- action: SnackBarAction(label: 'Retry', onPressed: () => handleTaskCompletion(context, completionCallback)),
+ action: SnackBarAction(
+ label: 'Retry',
+ onPressed: () => handleTaskCompletion(context, completionCallback),
+ ),
),
);
rethrow;
diff --git a/app/lib/theme.dart b/app/lib/theme.dart
index 37fc09fa3..b1b7a8859 100644
--- a/app/lib/theme.dart
+++ b/app/lib/theme.dart
@@ -19,7 +19,7 @@ ThemeData get theme => ThemeData(
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
- foregroundColor: MaterialStateProperty.all(Colors.white),
+ foregroundColor: WidgetStateProperty.all(Colors.white),
),
),
visualDensity: VisualDensity.adaptivePlatformDensity,
diff --git a/app/lib/util/app_analytics.dart b/app/lib/util/app_analytics.dart
index 2d11c4481..32871a838 100644
--- a/app/lib/util/app_analytics.dart
+++ b/app/lib/util/app_analytics.dart
@@ -4,11 +4,10 @@ import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_logging/sentry_logging.dart';
import 'package:studyu_app/app.dart';
import 'package:studyu_app/models/app_state.dart';
+import 'package:studyu_app/util/cache.dart';
import 'package:studyu_core/core.dart';
import 'package:studyu_flutter_common/studyu_flutter_common.dart';
-import 'cache.dart';
-
class AppAnalytics /*extends Analytics*/ {
static bool? _userEnabled;
@@ -35,8 +34,9 @@ class AppAnalytics /*extends Analytics*/ {
static Future start(AppConfig? appConfig, MyApp myApp) async {
StudyUAnalytics? studyUAnalytics;
- if (appConfig == null || appConfig.analytics != null && appConfig.analytics!.dsn.isEmpty) {
- final cachedAnalytics = (await Cache.loadAnalytics());
+ if (appConfig == null ||
+ appConfig.analytics != null && appConfig.analytics!.dsn.isEmpty) {
+ final cachedAnalytics = await Cache.loadAnalytics();
if (cachedAnalytics != null) {
studyUAnalytics = cachedAnalytics;
}
@@ -48,25 +48,30 @@ class AppAnalytics /*extends Analytics*/ {
runApp(myApp);
return;
}
- await SentryFlutter.init((options) {
- options.dsn = studyUAnalytics!.dsn;
- // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
- // We recommend adjusting this value in production.
- options.tracesSampleRate = studyUAnalytics.samplingRate ?? 1.0;
- options.addIntegration(LoggingIntegration());
- }, appRunner: () => runApp(myApp));
- Cache.storeAnalytics(StudyUAnalytics(
- studyUAnalytics.enabled,
- studyUAnalytics.dsn,
- studyUAnalytics.samplingRate,
- ));
+ await SentryFlutter.init(
+ (options) {
+ options.dsn = studyUAnalytics!.dsn;
+ // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
+ // We recommend adjusting this value in production.
+ options.tracesSampleRate = studyUAnalytics.samplingRate ?? 1.0;
+ options.addIntegration(LoggingIntegration());
+ },
+ appRunner: () => runApp(myApp),
+ );
+ Cache.storeAnalytics(
+ StudyUAnalytics(
+ studyUAnalytics.enabled,
+ studyUAnalytics.dsn,
+ studyUAnalytics.samplingRate,
+ ),
+ );
}
- static get isUserEnabled {
+ static bool? get isUserEnabled {
return _userEnabled;
}
- static void setEnabled(bool newEnabled) async {
+ static Future setEnabled(bool newEnabled) async {
await SecureStorage.write(keyAnalyticsUserEnable, newEnabled.toString());
if (!newEnabled) {
// a restart of the app will be necessary to enable sentry again
@@ -90,9 +95,11 @@ class AppAnalytics /*extends Analytics*/ {
void initAdvanced() {
Sentry.configureScope((scope) {
- scope.setUser(SentryUser(
- id: subject!.userId,
- ));
+ scope.setUser(
+ SentryUser(
+ id: subject!.userId,
+ ),
+ );
final advancedContext = {
'subjectId': subject!.id,
'studyId': state.selectedStudy!.id,
diff --git a/app/lib/util/cache.dart b/app/lib/util/cache.dart
index 751d6c9ab..4747459b6 100644
--- a/app/lib/util/cache.dart
+++ b/app/lib/util/cache.dart
@@ -19,19 +19,29 @@ class Cache {
static Future loadSubject() async {
// debugPrint("Load subject from cache");
if (await SecureStorage.containsKey(cacheSubjectKey)) {
- return StudySubject.fromJson(jsonDecode((await SecureStorage.read(cacheSubjectKey))!));
+ return StudySubject.fromJson(
+ jsonDecode((await SecureStorage.read(cacheSubjectKey))!)
+ as Map,
+ );
} else {
throw Exception("No cached subject found");
}
}
static Future storeAnalytics(StudyUAnalytics analytics) async {
- SecureStorage.write(StudyUAnalytics.keyStudyUAnalytics, jsonEncode(analytics.toJson()));
+ SecureStorage.write(
+ StudyUAnalytics.keyStudyUAnalytics,
+ jsonEncode(analytics.toJson()),
+ );
}
static Future loadAnalytics() async {
if (await SecureStorage.containsKey(cacheSubjectKey)) {
- return StudyUAnalytics.fromJson(jsonDecode((await SecureStorage.read(StudyUAnalytics.keyStudyUAnalytics))!));
+ return StudyUAnalytics.fromJson(
+ jsonDecode(
+ (await SecureStorage.read(StudyUAnalytics.keyStudyUAnalytics))!,
+ ) as Map,
+ );
}
return null;
}
@@ -45,7 +55,10 @@ class Cache {
final blobStorageHandler = BlobStorageHandler();
final futureBlobFiles = await TemporaryStorageHandler.getFutureBlobFiles();
for (final futureBlobFile in futureBlobFiles) {
- await blobStorageHandler.uploadObservation(futureBlobFile.futureBlobId, File(futureBlobFile.localFilePath));
+ await blobStorageHandler.uploadObservation(
+ futureBlobFile.futureBlobId,
+ File(futureBlobFile.localFilePath),
+ );
await File(futureBlobFile.localFilePath).delete();
}
}
@@ -53,12 +66,17 @@ class Cache {
static Future synchronize(StudySubject remoteSubject) async {
if (isSynchronizing) return remoteSubject;
// No local subject found
- if (!(await SecureStorage.containsKey(cacheSubjectKey))) return remoteSubject;
+ if (!(await SecureStorage.containsKey(cacheSubjectKey))) {
+ return remoteSubject;
+ }
final localSubject = await loadSubject();
// local and remote subject are equal, nothing to synchronize
if (localSubject == remoteSubject) return remoteSubject;
// remote subject belongs to a different study
- if (!kDebugMode && remoteSubject.startedAt!.isAfter(localSubject.startedAt!)) return remoteSubject;
+ if (!kDebugMode &&
+ remoteSubject.startedAt!.isAfter(localSubject.startedAt!)) {
+ return remoteSubject;
+ }
debugPrint("Synchronize subject with cache");
isSynchronizing = true;
@@ -82,17 +100,27 @@ class Cache {
newProgress = localSubject.progress;
}*/
// save new progress
- final List newProgress = [...localSubject.progress, ...remoteSubject.progress];
+ final List newProgress = [
+ ...localSubject.progress,
+ ...remoteSubject.progress,
+ ];
newProgress.removeWhere(
- (element) => localSubject.progress.contains(element) && remoteSubject.progress.contains(element));
- for (var p in newProgress) {
+ (element) =>
+ localSubject.progress.contains(element) &&
+ remoteSubject.progress.contains(element),
+ );
+ for (final p in newProgress) {
await p.save();
}
// merge local and remote progress and remove duplicates
- final List finalProgress = [...localSubject.progress, ...remoteSubject.progress];
+ final List finalProgress = [
+ ...localSubject.progress,
+ ...remoteSubject.progress,
+ ];
final duplicates = {};
- finalProgress.retainWhere((element) => duplicates.add(element.completedAt));
+ finalProgress
+ .retainWhere((element) => duplicates.add(element.completedAt));
// replace remote progress with our merge
remoteSubject.progress = finalProgress;
await remoteSubject.save();
@@ -101,10 +129,15 @@ class Cache {
// We can either drop local or overwrite remote
// ... for now do nothing
if (!kDebugMode && localSubject.startedAt == remoteSubject.startedAt) {
- StudyULogger.fatal("Cache synchronization found local changes that cannot be merged");
+ StudyULogger.fatal(
+ "Cache synchronization found local changes that cannot be merged",
+ );
StudyUDiagnostics.captureMessage(
- "localSubject: ${localSubject.toFullJson()} \nremoteSubject: ${remoteSubject.toFullJson()}");
- StudyUDiagnostics.captureException(Exception("CacheSynchronizationException"));
+ "localSubject: ${localSubject.toFullJson()} \nremoteSubject: ${remoteSubject.toFullJson()}",
+ );
+ StudyUDiagnostics.captureException(
+ Exception("CacheSynchronizationException"),
+ );
}
}
} catch (exception) {
diff --git a/app/lib/util/color.dart b/app/lib/util/color.dart
index c5fce8538..b83855db1 100644
--- a/app/lib/util/color.dart
+++ b/app/lib/util/color.dart
@@ -5,7 +5,8 @@ extension ColorBrightness on Color {
assert(amount >= 0 && amount <= 1);
final hsl = HSLColor.fromColor(this);
- final hslLight = hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0).toDouble());
+ final hslLight =
+ hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0));
return hslLight.toColor();
}
diff --git a/app/lib/util/data_processing.dart b/app/lib/util/data_processing.dart
index 82495e134..16487ddb5 100644
--- a/app/lib/util/data_processing.dart
+++ b/app/lib/util/data_processing.dart
@@ -15,7 +15,9 @@ class GroupedIterable extends Iterable>> {
Iterable> aggregate(FoldAggregator aggregator) =>
map((entry) => MapEntry(entry.key, aggregator(entry.value)));
- Iterable> aggregateWithKey(KeyedAggregator aggregator) =>
+ Iterable> aggregateWithKey