diff --git a/.github/workflows/default.yaml b/.github/workflows/default.yaml index 7c789d85..64fa60eb 100644 --- a/.github/workflows/default.yaml +++ b/.github/workflows/default.yaml @@ -10,14 +10,14 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' secrets: passphrase: ${{ secrets.PASSPHRASE }} flutter-build: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' android_output: 'apk' secrets: passphrase: ${{ secrets.PASSPHRASE }} \ No newline at end of file diff --git a/.github/workflows/dev.yaml b/.github/workflows/dev.yaml index ed5216cc..c9465037 100644 --- a/.github/workflows/dev.yaml +++ b/.github/workflows/dev.yaml @@ -9,7 +9,7 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' secrets: passphrase: ${{ secrets.PASSPHRASE }} page: @@ -18,7 +18,7 @@ jobs: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' android_output: 'aab' secrets: passphrase: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/fastlane.action.yaml b/.github/workflows/fastlane.action.yaml index 93accf78..e734eee3 100644 --- a/.github/workflows/fastlane.action.yaml +++ b/.github/workflows/fastlane.action.yaml @@ -34,7 +34,7 @@ jobs: - name: 'Generate changelog' run: ./.github/scripts/generate_changelog.sh - name: 'Setup Ruby' - uses: ruby/setup-ruby@v1.148.0 + uses: ruby/setup-ruby@v1.149.0 with: ruby-version: '3.0' bundler-cache: true diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 5db0da7a..fd077079 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -9,14 +9,14 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' secrets: passphrase: ${{ secrets.PASSPHRASE }} flutter-build: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' android_output: 'aab' secrets: passphrase: ${{ secrets.PASSPHRASE }} diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml index 1491e7d4..33c3233b 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -9,14 +9,14 @@ jobs: flutter-test-analyze: uses: ./.github/workflows/flutter.analyze-test.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' secrets: passphrase: ${{ secrets.PASSPHRASE }} flutter-build: needs: [flutter-test-analyze] uses: ./.github/workflows/flutter.build.action.yaml with: - flutter_version: '3.7.7' + flutter_version: '3.10.0' android_output: 'aab' secrets: passphrase: ${{ secrets.PASSPHRASE }} diff --git a/.gitignore b/.gitignore index 504d1e56..fc773cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,47 +1,50 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ -migrate_working_dir/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.packages -.pub-cache/ -.pub/ -/build/ - -# Web related -lib/generated_plugin_registrant.dart - -# Symbolication related -app.*.symbols - -# Obfuscation related -app.*.map.json - -# Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +# Code coverage +**/lcov.info diff --git a/CHANGELOG_en.md b/CHANGELOG_en.md index c22a47a6..b8f8fc51 100644 --- a/CHANGELOG_en.md +++ b/CHANGELOG_en.md @@ -1,3 +1,21 @@ +# **v1.8.1**: + +- *Fix*: + - Slots on two-day events are now functioning normally +- *Interface*: + - Added support for Android tablets +*** +# **v1.8.0** : + +- *Fix*: + - Certain niches were not taken into account properly +- *Interface*: + - Past closures are no longer displayed + - Main window is more organic + - Added a button to go directly to check the configured slots +- *Core*: + - Update to Flutter 3.10.0 +*** # **v1.6.1** : - *Fix*: diff --git a/CHANGELOG_es.md b/CHANGELOG_es.md index fe6d1750..990f8523 100644 --- a/CHANGELOG_es.md +++ b/CHANGELOG_es.md @@ -1,3 +1,21 @@ +# **v1.8.1**: + +- *Fix*: + - Las franjas horarias para eventos de dos días ya funcionan normalmente +- *Interfaz*: + - Añadido soporte para tabletas Android +*** +# **v1.8.0** : + +- *Fix*: + - Algunas franjas horarias no se tuvieron debidamente en cuenta +- *Interfaz*: + - Los cierres pasados ya no se muestran + - La ventana principal es más orgánica + - Se agregó un botón para ir directamente a comprobar las ranuras configuradas +- *Core*: + - Actualización a Flutter 3.10.0 +*** # **v1.6.1** : - *Fix*: diff --git a/CHANGELOG_fr.md b/CHANGELOG_fr.md index 2fd68345..d0b00cc4 100644 --- a/CHANGELOG_fr.md +++ b/CHANGELOG_fr.md @@ -1,3 +1,21 @@ +# **v1.8.1** : + +- *Fix*: + - Les créneaux horaires sur des évènements d'étalant sur deux jours fonctionnent maintenant normalement +- *Interface*: + - Ajout du support pour les tablettes +*** +# **v1.8.0** : + +- *Fix*: + - Certains créneux n'étaient pas pris bien pris en compte +- *Interface*: + - Les fermetures passées ne sont plus affichées + - La fenêtre principale est plus organique + - Ajout d'un bouton permettant d'aller directement vérifier les créneaux configurés +- *Core*: + - Mise à jour en Flutter 3.10.0 +*** # **v1.6.1** : - *Fix*: diff --git a/README.md b/README.md index a866f235..9767eb9b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ +

Mobile app to track the schedules of the [Chaban Delmas bridge](https://fr.wikipedia.org/wiki/Pont_Jacques-Chaban-Delmas) located in Bordeaux, France. diff --git a/android/build.gradle b/android/build.gradle index 13b3050e..d02fac26 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,31 +1,31 @@ -buildscript { - ext.kotlin_version = '1.8.21' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = '../build' -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(':app') -} - -task clean(type: Delete) { - delete rootProject.buildDir -} +buildscript { + ext.kotlin_version = '1.8.21' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +tasks.register("clean", Delete) { + delete rootProject.buildDir +} diff --git a/android/fastlane/metadata/android/en-GB/full_description.txt b/android/fastlane/metadata/android/en-GB/full_description.txt index 108d4bdc..4a863287 100644 --- a/android/fastlane/metadata/android/en-GB/full_description.txt +++ b/android/fastlane/metadata/android/en-GB/full_description.txt @@ -6,4 +6,6 @@ Several types of notifications are available: - One day before closing at the time you want - A summary of upcoming closures for the coming week -Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever and does not represent any government entity. \ No newline at end of file +You also have the possibility to fill in slots in order to be informed only of events that impact you! + +Disclaimer: Chabo uses the web services provided by Bordeaux Métropole. It does not represent any government entity or the agglomeration of Bordeaux Métropole. \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/1_en-GB.png b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/1_en-GB.png index e48230cd..11cdfedf 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/1_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/1_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/2_en-GB.png b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/2_en-GB.png index 9228c31b..fc2cd8e8 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/2_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/2_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/3_en-GB.png b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/3_en-GB.png index 75ac1c9d..a3a57b5d 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/3_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/3_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/4_en-GB.png b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/4_en-GB.png index 2c458fa5..07f9233d 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/4_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/phoneScreenshots/4_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/1_en-GB.png b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/1_en-GB.png index e48230cd..5b425b21 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/1_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/1_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/2_en-GB.png b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/2_en-GB.png index 9228c31b..c615aa4b 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/2_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/2_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/3_en-GB.png b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/3_en-GB.png index 75ac1c9d..4fb82428 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/3_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/3_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/4_en-GB.png b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/4_en-GB.png index 2c458fa5..d1f60778 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/4_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/sevenInchScreenshots/4_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/1_en-GB.png b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/1_en-GB.png index e48230cd..6b116eb5 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/1_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/1_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/2_en-GB.png b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/2_en-GB.png index 9228c31b..3aeb8e06 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/2_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/2_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/3_en-GB.png b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/3_en-GB.png index 75ac1c9d..af76e0f0 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/3_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/3_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/4_en-GB.png b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/4_en-GB.png index 2c458fa5..766354aa 100644 Binary files a/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/4_en-GB.png and b/android/fastlane/metadata/android/en-GB/images/tenInchScreenshots/4_en-GB.png differ diff --git a/android/fastlane/metadata/android/en-GB/short_description.txt b/android/fastlane/metadata/android/en-GB/short_description.txt index 102a7680..dfcd6029 100644 --- a/android/fastlane/metadata/android/en-GB/short_description.txt +++ b/android/fastlane/metadata/android/en-GB/short_description.txt @@ -1 +1 @@ -Chabo, the mobile app that gives you the timetables of the Chaban bridge \ No newline at end of file +Chabo, the application that allows you to know the events of the Chaban bridge \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/full_description.txt b/android/fastlane/metadata/android/en-US/full_description.txt index 108d4bdc..4a863287 100644 --- a/android/fastlane/metadata/android/en-US/full_description.txt +++ b/android/fastlane/metadata/android/en-US/full_description.txt @@ -6,4 +6,6 @@ Several types of notifications are available: - One day before closing at the time you want - A summary of upcoming closures for the coming week -Disclaimer: Chabo uses the web services provided by Bordeaux Métropole but is in no way affiliated with the latter or any other public entity whatsoever and does not represent any government entity. \ No newline at end of file +You also have the possibility to fill in slots in order to be informed only of events that impact you! + +Disclaimer: Chabo uses the web services provided by Bordeaux Métropole. It does not represent any government entity or the agglomeration of Bordeaux Métropole. \ No newline at end of file diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png index e48230cd..11cdfedf 100644 Binary files a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/1_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png index 9228c31b..fc2cd8e8 100644 Binary files a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png index 75ac1c9d..a3a57b5d 100644 Binary files a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/3_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png index 2c458fa5..07f9233d 100644 Binary files a/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png and b/android/fastlane/metadata/android/en-US/images/phoneScreenshots/4_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1_en-US.png b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1_en-US.png index e48230cd..5b425b21 100644 Binary files a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1_en-US.png and b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/1_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2_en-US.png b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2_en-US.png index 9228c31b..c615aa4b 100644 Binary files a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2_en-US.png and b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/2_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3_en-US.png b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3_en-US.png index 75ac1c9d..4fb82428 100644 Binary files a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3_en-US.png and b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/3_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/4_en-US.png b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/4_en-US.png index 2c458fa5..d1f60778 100644 Binary files a/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/4_en-US.png and b/android/fastlane/metadata/android/en-US/images/sevenInchScreenshots/4_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png index e48230cd..6b116eb5 100644 Binary files a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/1_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/2_en-US.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/2_en-US.png index 9228c31b..3aeb8e06 100644 Binary files a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/2_en-US.png and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/2_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/3_en-US.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/3_en-US.png index 75ac1c9d..af76e0f0 100644 Binary files a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/3_en-US.png and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/3_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/4_en-US.png b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/4_en-US.png index 2c458fa5..766354aa 100644 Binary files a/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/4_en-US.png and b/android/fastlane/metadata/android/en-US/images/tenInchScreenshots/4_en-US.png differ diff --git a/android/fastlane/metadata/android/en-US/short_description.txt b/android/fastlane/metadata/android/en-US/short_description.txt index 102a7680..dfcd6029 100644 --- a/android/fastlane/metadata/android/en-US/short_description.txt +++ b/android/fastlane/metadata/android/en-US/short_description.txt @@ -1 +1 @@ -Chabo, the mobile app that gives you the timetables of the Chaban bridge \ No newline at end of file +Chabo, the application that allows you to know the events of the Chaban bridge \ No newline at end of file diff --git a/android/fastlane/metadata/android/es-ES/full_description.txt b/android/fastlane/metadata/android/es-ES/full_description.txt index 17440558..98e4d2a3 100644 --- a/android/fastlane/metadata/android/es-ES/full_description.txt +++ b/android/fastlane/metadata/android/es-ES/full_description.txt @@ -6,4 +6,6 @@ Hay varios tipos de notificaciones disponibles: - Un día antes del cierre a la hora que quieras - Un resumen de los próximos cierres para la próxima semana. -Descargo de responsabilidad: Chabo utiliza los servicios web proporcionados por Bordeaux Métropole, pero de ninguna manera está afiliado a este último ni a ninguna otra entidad pública y no representa a ninguna entidad gubernamental. \ No newline at end of file +¡También tienes la opción de introducir franjas horarias para que solo te avisen de los eventos que te afectan! + +Descargo de responsabilidad: Chabo utiliza los servicios web proporcionados por Bordeaux Métropole. No representa ninguna entidad gubernamental ni la aglomeración de Burdeos. \ No newline at end of file diff --git a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/1_es-ES.png b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/1_es-ES.png index 19d95bb5..73c69f9c 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/1_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/1_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/2_es-ES.png b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/2_es-ES.png index bc3d7564..9962276a 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/2_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/2_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/3_es-ES.png b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/3_es-ES.png index e9ed4878..b84acaa6 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/3_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/3_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/4_es-ES.png b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/4_es-ES.png index 1dfe12cd..2e73e868 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/4_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/phoneScreenshots/4_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/1_es-ES.png b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/1_es-ES.png index 19d95bb5..46bfbbeb 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/1_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/1_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/2_es-ES.png b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/2_es-ES.png index bc3d7564..4b6727fc 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/2_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/2_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/3_es-ES.png b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/3_es-ES.png index e9ed4878..dd761683 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/3_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/3_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/4_es-ES.png b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/4_es-ES.png index 1dfe12cd..9696928e 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/4_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/sevenInchScreenshots/4_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/1_es-ES.png b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/1_es-ES.png index 19d95bb5..0e5f13bf 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/1_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/1_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/2_es-ES.png b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/2_es-ES.png index bc3d7564..a8e78e85 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/2_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/2_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/3_es-ES.png b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/3_es-ES.png index e9ed4878..a091097e 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/3_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/3_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/4_es-ES.png b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/4_es-ES.png index 1dfe12cd..a2fd387f 100644 Binary files a/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/4_es-ES.png and b/android/fastlane/metadata/android/es-ES/images/tenInchScreenshots/4_es-ES.png differ diff --git a/android/fastlane/metadata/android/es-ES/short_description.txt b/android/fastlane/metadata/android/es-ES/short_description.txt index 5baa2790..d125e6c7 100644 --- a/android/fastlane/metadata/android/es-ES/short_description.txt +++ b/android/fastlane/metadata/android/es-ES/short_description.txt @@ -1 +1 @@ -Chabo, la aplicación que permite conocer los horarios del puente Chaban \ No newline at end of file +Chabo, la aplicación que te permite conocer los eventos del puente Chaban \ No newline at end of file diff --git a/android/fastlane/metadata/android/fr-FR/full_description.txt b/android/fastlane/metadata/android/fr-FR/full_description.txt index 1aed863c..f50ec670 100644 --- a/android/fastlane/metadata/android/fr-FR/full_description.txt +++ b/android/fastlane/metadata/android/fr-FR/full_description.txt @@ -5,5 +5,7 @@ Plusieurs types de notifications sont disponibles : - Le jour de la fermeture au moment où vous le souhaitez - Un jour avant fermeture à l'heure que vous souhaitez - Un récapitulatif des fermetures à venir pour la semaine qui arrive + +Vous avez aussi la possibilité de renseigner des créneaux horaires afin d'être uniquement avertis des évènements qui vous impactent ! -Avis de non-responsabilité : Chabo utilise les services web fournis par Bordeaux métropole mais n'est en aucun cas affilié à cette dernière ou à aucune autre entité publique que ce soit et ne représente aucune entité gouvernementale \ No newline at end of file +Avis de non-responsabilité : Chabo utilise les services web fournis par Bordeaux Métropole. Elle ne représente aucune entité gouvernementale ni l'agglomération de Bordeaux Métropole. \ No newline at end of file diff --git a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1_fr-FR.png index ed40e494..00d7df4e 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/1_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2_fr-FR.png index e346dbf9..96ddf6ca 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/2_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3_fr-FR.png index 351bec2e..0f3e6e8e 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/3_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4_fr-FR.png index c7f4c9b0..9556ab8a 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/phoneScreenshots/4_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/1_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/1_fr-FR.png index ed40e494..80965f0f 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/1_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/1_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/2_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/2_fr-FR.png index e346dbf9..89c2486c 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/2_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/2_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/3_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/3_fr-FR.png index 351bec2e..c7957694 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/3_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/3_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/4_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/4_fr-FR.png index c7f4c9b0..de82613a 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/4_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/sevenInchScreenshots/4_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/1_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/1_fr-FR.png index ed40e494..cd389dbf 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/1_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/1_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/2_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/2_fr-FR.png index e346dbf9..430dcefb 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/2_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/2_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/3_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/3_fr-FR.png index 351bec2e..74a409e6 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/3_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/3_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/4_fr-FR.png b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/4_fr-FR.png index c7f4c9b0..d8fd8256 100644 Binary files a/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/4_fr-FR.png and b/android/fastlane/metadata/android/fr-FR/images/tenInchScreenshots/4_fr-FR.png differ diff --git a/android/fastlane/metadata/android/fr-FR/short_description.txt b/android/fastlane/metadata/android/fr-FR/short_description.txt index 16c7be9c..e8759d7b 100644 --- a/android/fastlane/metadata/android/fr-FR/short_description.txt +++ b/android/fastlane/metadata/android/fr-FR/short_description.txt @@ -1 +1 @@ -Chabo, l'application qui vous permet de connaître les horaires du pont Chaban \ No newline at end of file +Chabo, l'application qui vous permet de connaître les évènements du pont Chaban \ No newline at end of file diff --git a/lib/bloc/scroll_status/scroll_status_bloc.dart b/lib/bloc/scroll_status/scroll_status_bloc.dart index f56c9c17..6f9d6a98 100644 --- a/lib/bloc/scroll_status/scroll_status_bloc.dart +++ b/lib/bloc/scroll_status/scroll_status_bloc.dart @@ -1,17 +1,20 @@ import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:chabo/bloc/chabo_event.dart'; +import 'package:chabo/custom_properties.dart'; import 'package:chabo/models/abstract_forecast.dart'; +import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; part 'scroll_status_event.dart'; + part 'scroll_status_state.dart'; class ScrollStatusBloc extends Bloc { final ScrollController scrollController; ScrollStatusBloc({required this.scrollController}) - : super(ScrollStatusState( + : super(const ScrollStatusState( showCurrentStatus: true, status: ScrollStatus.ok, currentTarget: null, @@ -34,7 +37,6 @@ class ScrollStatusBloc extends Bloc { state.copyWith( showCurrentStatus: true, status: ScrollStatus.ok, - currentTarget: state.currentTarget, ), ); } @@ -50,7 +52,9 @@ class ScrollStatusBloc extends Bloc { await scrollController.animateTo( pixel, - duration: const Duration(milliseconds: 300), + duration: const Duration( + milliseconds: CustomProperties.animationDurationMs, + ), curve: Curves.linear, ); targetContext = GlobalObjectKey(event.goTo.hashCode).currentContext; diff --git a/lib/bloc/scroll_status/scroll_status_state.dart b/lib/bloc/scroll_status/scroll_status_state.dart index df7344ab..f1d97b6d 100644 --- a/lib/bloc/scroll_status/scroll_status_state.dart +++ b/lib/bloc/scroll_status/scroll_status_state.dart @@ -1,11 +1,11 @@ part of 'scroll_status_bloc.dart'; -class ScrollStatusState { +class ScrollStatusState extends Equatable { final AbstractForecast? currentTarget; final bool showCurrentStatus; final ScrollStatus status; - ScrollStatusState({ + const ScrollStatusState({ required this.status, required this.showCurrentStatus, required this.currentTarget, @@ -22,6 +22,9 @@ class ScrollStatusState { currentTarget: currentTarget ?? this.currentTarget, ); } + + @override + List get props => [currentTarget, showCurrentStatus, status]; } enum ScrollStatus { ok, error } diff --git a/lib/bloc/status/status_bloc.dart b/lib/bloc/status/status_bloc.dart index 99111b4b..8379139a 100644 --- a/lib/bloc/status/status_bloc.dart +++ b/lib/bloc/status/status_bloc.dart @@ -10,6 +10,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; part 'status_event.dart'; + part 'status_state.dart'; class StatusBloc extends Bloc { @@ -23,6 +24,23 @@ class StatusBloc extends Bloc { on( _onDurationChanged, ); + on( + _onStatusWidgetDimensionChanged, + ); + } + + void _onStatusWidgetDimensionChanged( + StatusWidgetDimensionChanged event, + Emitter emit, + ) { + emit( + state.copyWith( + statusWidgetDimension: event.dimension, + mainMessageStatus: _getMainStatus( + event.context, + ), + ), + ); } void _onDurationChanged( @@ -133,14 +151,20 @@ class StatusBloc extends Bloc { !currentForecast.isCurrentlyClosed() && state.durationUntilNextEvent.inMinutes >= state.durationForCloseClosing.inMinutes) { - return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.open}'; + return state.statusWidgetDimension == StatusWidgetDimension.large + ? '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.open}' + : AppLocalizations.of(context)!.open.capitalize(); } else if (currentForecast != null && !currentForecast.isCurrentlyClosed() && state.durationUntilNextEvent.inMinutes < state.durationForCloseClosing.inMinutes) { - return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.aboutToClose}'; + return state.statusWidgetDimension == StatusWidgetDimension.large + ? '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.aboutToClose}' + : AppLocalizations.of(context)!.aboutToClose.capitalize(); } else { - return '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}'; + return state.statusWidgetDimension == StatusWidgetDimension.large + ? '${_getGreetings(context)}, ${AppLocalizations.of(context)!.theBridgeIsCurrently} ${AppLocalizations.of(context)!.closed}' + : AppLocalizations.of(context)!.closed.capitalize(); } } diff --git a/lib/bloc/status/status_event.dart b/lib/bloc/status/status_event.dart index fbb1f278..fcd36a43 100644 --- a/lib/bloc/status/status_event.dart +++ b/lib/bloc/status/status_event.dart @@ -27,3 +27,13 @@ class StatusDurationChanged extends StatusEvent { required this.duration, }) : super(); } + +class StatusWidgetDimensionChanged extends StatusEvent { + final StatusWidgetDimension dimension; + final BuildContext context; + + StatusWidgetDimensionChanged({ + required this.dimension, + required this.context, + }) : super(); +} diff --git a/lib/bloc/status/status_state.dart b/lib/bloc/status/status_state.dart index 318f6b35..012281f1 100644 --- a/lib/bloc/status/status_state.dart +++ b/lib/bloc/status/status_state.dart @@ -12,6 +12,7 @@ class StatusState extends Equatable { final String timeMessagePrefix; final Color foregroundColor; final Color backgroundColor; + final StatusWidgetDimension statusWidgetDimension; const StatusState({ required this.statusLifecycle, @@ -25,6 +26,7 @@ class StatusState extends Equatable { required this.timeMessagePrefix, required this.foregroundColor, required this.backgroundColor, + required this.statusWidgetDimension, }); StatusState copyWith({ @@ -39,6 +41,7 @@ class StatusState extends Equatable { String? timeMessagePrefix, Color? foregroundColor, Color? backgroundColor, + StatusWidgetDimension? statusWidgetDimension, }) { return StatusState( statusLifecycle: statusLifecycle ?? this.statusLifecycle, @@ -56,6 +59,8 @@ class StatusState extends Equatable { timeMessagePrefix: timeMessagePrefix ?? this.timeMessagePrefix, foregroundColor: foregroundColor ?? this.foregroundColor, backgroundColor: backgroundColor ?? this.backgroundColor, + statusWidgetDimension: + statusWidgetDimension ?? this.statusWidgetDimension, ); } @@ -72,6 +77,7 @@ class StatusState extends Equatable { timeMessagePrefix, foregroundColor, backgroundColor, + statusWidgetDimension, ]; } @@ -89,7 +95,10 @@ class StatusStateInitial extends StatusState { timeMessagePrefix: '', foregroundColor: Colors.white, backgroundColor: Colors.white, + statusWidgetDimension: StatusWidgetDimension.large, ); } enum StatusLifecycle { empty, populated } + +enum StatusWidgetDimension { small, large } diff --git a/lib/chabo.dart b/lib/chabo.dart index a09ebe5d..28951ee3 100644 --- a/lib/chabo.dart +++ b/lib/chabo.dart @@ -1,13 +1,16 @@ +import 'package:chabo/app_theme.dart'; import 'package:chabo/bloc/forecast/forecast_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/bloc/status/status_bloc.dart'; import 'package:chabo/bloc/theme/theme_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; +import 'package:chabo/helpers/device_helper.dart'; import 'package:chabo/screens/forecast_screen.dart'; import 'package:chabo/service/notification_service.dart'; import 'package:chabo/service/storage_service.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -25,6 +28,8 @@ class Chabo extends StatelessWidget { @override Widget build(BuildContext context) { + DeviceHelper.computePreferredOrientation(context); + return MultiBlocProvider( providers: [ /// Bloc intended to manage the theme of the App @@ -77,6 +82,16 @@ class Chabo extends StatelessWidget { ], child: BlocBuilder( builder: (context, state) { + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + systemStatusBarContrastEnforced: false, + statusBarIconBrightness: state.themeData == AppTheme.darkTheme + ? Brightness.light + : Brightness.dark, + ), + ); + return MaterialApp( debugShowCheckedModeBanner: false, theme: state.themeData, diff --git a/lib/custom_properties.dart b/lib/custom_properties.dart index f4a70526..b94dc9bc 100644 --- a/lib/custom_properties.dart +++ b/lib/custom_properties.dart @@ -1,6 +1,7 @@ class CustomProperties { static const double borderRadius = 17; - static const int animationDurationMs = 200; + static const int animationDurationMs = 400; + static const int shortAnimationDurationMs = 200; static const double blurSigmaX = 4; static const double blurSigmaY = 4; diff --git a/lib/dialogs/chabo_about_dialog.dart b/lib/dialogs/chabo_about_dialog.dart deleted file mode 100644 index a3b4c502..00000000 --- a/lib/dialogs/chabo_about_dialog.dart +++ /dev/null @@ -1,283 +0,0 @@ -import 'package:chabo/const.dart'; -import 'package:chabo/custom_properties.dart'; -import 'package:chabo/screens/changelog_screen.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:package_info_plus/package_info_plus.dart'; - -class ChaboAboutDialog extends StatelessWidget { - final Widget _iconWidget = Padding( - padding: const EdgeInsets.all(5), - child: SizedBox( - height: 60, - width: 60, - child: Image.asset(Const.appLogoPath), - ), - ); - - ChaboAboutDialog({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final textTheme = Theme.of(context).textTheme; - - return FutureBuilder( - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } - if (snapshot.connectionState == ConnectionState.none && - snapshot.data == null) { - return Text(AppLocalizations.of(context)!.unableAppInfo); - } - - return AlertDialog( - insetPadding: const EdgeInsets.symmetric(horizontal: 20), - titlePadding: const EdgeInsets.all(20), - contentPadding: const EdgeInsets.symmetric(horizontal: 20), - actionsPadding: const EdgeInsets.fromLTRB(0, 10, 20, 20), - title: Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - IconTheme( - data: Theme.of(context).iconTheme, - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, - ), - ), - ), - child: _iconWidget, - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - snapshot.data!.appName, - style: Theme.of(context) - .textTheme - .headlineSmall! - .copyWith( - fontWeight: FontWeight.bold, - fontSize: 30, - ), - ), - Text( - ' | v${snapshot.data!.version} (${snapshot.data!.buildNumber})', - style: textTheme.bodyMedium, - ), - ], - ), - Text( - Const.legalLease, - style: textTheme.bodySmall!.copyWith(), - ), - ], - ), - ), - ), - ], - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, - ), - ), - content: ListBody( - children: [ - Text( - AppLocalizations.of(context)!.appDescription, - style: textTheme.bodyLarge, - ), - const SizedBox( - height: 15, - ), - Text( - AppLocalizations.of(context)!.disclaimer, - style: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(fontStyle: FontStyle.italic), - ), - const SizedBox( - height: 15, - ), - Wrap( - alignment: WrapAlignment.center, - spacing: 10, - runSpacing: 10, - children: Const.usefulLinks - .map( - (link) => ElevatedButton( - onPressed: () => link.launchURL(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - link.iconData, - size: 20, - ), - const SizedBox( - width: 10, - ), - Padding( - padding: - const EdgeInsets.symmetric(vertical: 8.0), - child: Text( - AppLocalizations.of(context)!.selectAboutDialog( - link.translationKey, - ), - ), - ), - const SizedBox( - width: 10, - ), - ], - ), - ), - ) - .toList(), - ), - const SizedBox( - height: 10, - ), - Wrap( - spacing: 10, - runSpacing: 5, - alignment: WrapAlignment.center, - children: [ - ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - colorScheme.secondaryContainer, - ), - foregroundColor: MaterialStateProperty.all( - colorScheme.onSecondaryContainer, - ), - ), - onPressed: () => Navigator.push( - context, - MaterialPageRoute( - builder: (context) => const ChangeLogScreen(), - ), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - FontAwesomeIcons.codeMerge, - size: 20, - ), - const SizedBox( - width: 10, - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text( - AppLocalizations.of(context)!.selectAboutDialog( - 'changelog', - ), - ), - ), - ], - ), - ), - ElevatedButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - colorScheme.secondaryContainer, - ), - foregroundColor: MaterialStateProperty.all( - colorScheme.onSecondaryContainer, - ), - ), - onPressed: () { - showLicensePage( - context: context, - applicationName: snapshot.data!.appName, - applicationVersion: - 'v${snapshot.data!.version}+${snapshot.data!.buildNumber}', - applicationIcon: Padding( - padding: const EdgeInsets.all(8.0), - child: IconTheme( - data: Theme.of(context).iconTheme, - child: Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, - ), - ), - ), - child: _iconWidget, - ), - ), - ), - applicationLegalese: Const.legalLease, - ); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon( - FontAwesomeIcons.fileLines, - size: 20, - ), - const SizedBox( - width: 10, - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Text( - AppLocalizations.of(context)! - .selectAboutDialog('licenses'), - ), - ), - ], - ), - ), - ], - ), - ], - ), - actions: [ - ElevatedButton.icon( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(Colors.white), - foregroundColor: MaterialStateProperty.all( - Theme.of(context).primaryColor, - ), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - CustomProperties.borderRadius, - ), - ), - ), - ), - onPressed: () => {Navigator.pop(context)}, - icon: const Icon(Icons.close), - label: Text( - MaterialLocalizations.of(context).closeButtonLabel, - ), - ), - ], - scrollable: true, - ); - }, - future: PackageInfo.fromPlatform(), - ); - } -} diff --git a/lib/dialogs/chabo_about_dialog/chabo_about_dialog.dart b/lib/dialogs/chabo_about_dialog/chabo_about_dialog.dart new file mode 100644 index 00000000..a90e79cc --- /dev/null +++ b/lib/dialogs/chabo_about_dialog/chabo_about_dialog.dart @@ -0,0 +1,197 @@ +import 'package:chabo/const.dart'; +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/helpers/device_helper.dart'; +import 'package:chabo/screens/changelog_screen.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +part 'close_button.dart'; + +part 'page_links_widget.dart'; + +part 'web_links_widget.dart'; + +class ChaboAboutDialog extends StatelessWidget { + final Widget _iconWidget = Padding( + padding: const EdgeInsets.all(5), + child: SizedBox( + height: 60, + width: 60, + child: Image.asset(Const.appLogoPath), + ), + ); + + ChaboAboutDialog({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return FutureBuilder( + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } + if (snapshot.connectionState == ConnectionState.none && + snapshot.data == null) { + return Text(AppLocalizations.of(context)!.unableAppInfo); + } + + return AlertDialog( + insetPadding: const EdgeInsets.symmetric(horizontal: 20), + titlePadding: const EdgeInsets.all(20), + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + ), + actionsPadding: const EdgeInsets.fromLTRB(0, 10, 20, 20), + title: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + IconTheme( + data: Theme.of(context).iconTheme, + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + ), + child: _iconWidget, + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + snapshot.data!.appName, + style: Theme.of(context) + .textTheme + .headlineSmall! + .copyWith( + fontWeight: FontWeight.bold, + fontSize: 30, + ), + ), + Text( + ' | v${snapshot.data!.version} (${snapshot.data!.buildNumber})', + style: textTheme.bodyMedium, + ), + ], + ), + Text( + Const.legalLease, + style: textTheme.bodySmall!.copyWith(), + ), + ], + ), + ), + ), + !DeviceHelper.isPortrait(context) + ? const _CloseButton() + : const SizedBox.shrink(), + ], + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + CustomProperties.borderRadius, + ), + ), + content: Container( + constraints: DeviceHelper.isMobile(context) + ? DeviceHelper.isPortrait(context) + ? null + : BoxConstraints( + maxWidth: MediaQuery.of(context).size.width / 1.2, + ) + : BoxConstraints( + maxWidth: MediaQuery.of(context).size.width / 1.9, + ), + child: DeviceHelper.isPortrait(context) + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.appDescription, + style: textTheme.bodyLarge, + ), + const SizedBox( + height: 15, + ), + Text( + AppLocalizations.of(context)!.disclaimer, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontStyle: FontStyle.italic), + ), + const SizedBox( + height: 15, + ), + _PageLinksWidget( + packageInfo: snapshot.data!, + iconWidget: _iconWidget, + ), + const SizedBox( + height: 15, + ), + const _WebLinksWidget(), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + AppLocalizations.of(context)!.appDescription, + style: textTheme.bodyLarge, + ), + const SizedBox( + height: 15, + ), + Text( + AppLocalizations.of(context)!.disclaimer, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontStyle: FontStyle.italic), + ), + const SizedBox( + height: 15, + ), + _PageLinksWidget( + packageInfo: snapshot.data!, + iconWidget: _iconWidget, + ), + const SizedBox( + height: 15, + ), + ], + ), + ), + const Flexible( + child: _WebLinksWidget(), + ), + ], + ), + ), + scrollable: true, + actions: + DeviceHelper.isPortrait(context) ? [const _CloseButton()] : [], + ); + }, + future: PackageInfo.fromPlatform(), + ); + } +} diff --git a/lib/dialogs/chabo_about_dialog/close_button.dart b/lib/dialogs/chabo_about_dialog/close_button.dart new file mode 100644 index 00000000..c5ad6805 --- /dev/null +++ b/lib/dialogs/chabo_about_dialog/close_button.dart @@ -0,0 +1,29 @@ +part of 'chabo_about_dialog.dart'; + +class _CloseButton extends StatelessWidget { + const _CloseButton({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ElevatedButton.icon( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Colors.white), + foregroundColor: MaterialStateProperty.all( + Theme.of(context).primaryColor, + ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + CustomProperties.borderRadius, + ), + ), + ), + ), + onPressed: () => {Navigator.pop(context)}, + icon: const Icon(Icons.close), + label: Text( + MaterialLocalizations.of(context).closeButtonLabel, + ), + ); + } +} diff --git a/lib/dialogs/chabo_about_dialog/page_links_widget.dart b/lib/dialogs/chabo_about_dialog/page_links_widget.dart new file mode 100644 index 00000000..78cafdb6 --- /dev/null +++ b/lib/dialogs/chabo_about_dialog/page_links_widget.dart @@ -0,0 +1,115 @@ +part of 'chabo_about_dialog.dart'; + +class _PageLinksWidget extends StatelessWidget { + final PackageInfo packageInfo; + final Widget iconWidget; + + const _PageLinksWidget({ + Key? key, + required this.packageInfo, + required this.iconWidget, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Wrap( + spacing: 10, + runSpacing: 5, + alignment: WrapAlignment.center, + children: [ + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + colorScheme.secondaryContainer, + ), + foregroundColor: MaterialStateProperty.all( + colorScheme.onSecondaryContainer, + ), + ), + onPressed: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const ChangeLogScreen(), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + FontAwesomeIcons.codeMerge, + size: 20, + ), + const SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + AppLocalizations.of(context)!.selectAboutDialog( + 'changelog', + ), + ), + ), + ], + ), + ), + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + colorScheme.secondaryContainer, + ), + foregroundColor: MaterialStateProperty.all( + colorScheme.onSecondaryContainer, + ), + ), + onPressed: () { + showLicensePage( + context: context, + applicationName: packageInfo.appName, + applicationVersion: + 'v${packageInfo.version}+${packageInfo.buildNumber}', + applicationIcon: Padding( + padding: const EdgeInsets.all(8.0), + child: IconTheme( + data: Theme.of(context).iconTheme, + child: Container( + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + ), + child: iconWidget, + ), + ), + ), + applicationLegalese: Const.legalLease, + ); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + FontAwesomeIcons.fileLines, + size: 20, + ), + const SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + AppLocalizations.of(context)!.selectAboutDialog('licenses'), + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/dialogs/chabo_about_dialog/web_links_widget.dart b/lib/dialogs/chabo_about_dialog/web_links_widget.dart new file mode 100644 index 00000000..f9003c2f --- /dev/null +++ b/lib/dialogs/chabo_about_dialog/web_links_widget.dart @@ -0,0 +1,48 @@ +part of 'chabo_about_dialog.dart'; + +class _WebLinksWidget extends StatelessWidget { + const _WebLinksWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Wrap( + alignment: WrapAlignment.center, + spacing: 10, + runSpacing: 10, + children: Const.usefulLinks + .map( + (link) => ElevatedButton( + onPressed: () => link.launchURL(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + link.iconData, + size: 20, + ), + const SizedBox( + width: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + AppLocalizations.of(context)!.selectAboutDialog( + link.translationKey, + ), + ), + ), + const SizedBox( + width: 10, + ), + ], + ), + ), + ) + .toList(), + ), + ], + ); + } +} diff --git a/lib/dialogs/days_of_the_week_dialog.dart b/lib/dialogs/days_of_the_week_dialog.dart index cf550f2d..9c7dc1af 100644 --- a/lib/dialogs/days_of_the_week_dialog.dart +++ b/lib/dialogs/days_of_the_week_dialog.dart @@ -1,4 +1,5 @@ import 'package:chabo/bloc/notification/notification_bloc.dart'; +import 'package:chabo/custom_properties.dart'; import 'package:chabo/models/enums/day.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,7 +14,7 @@ class DaysOfTheWeekDialog extends StatelessWidget { contentPadding: const EdgeInsets.all(15), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( - 15, + CustomProperties.borderRadius, ), ), content: BlocBuilder( diff --git a/lib/dialogs/forecast_information_dialog.dart b/lib/dialogs/forecast_information_dialog.dart index 982deb62..bb750130 100644 --- a/lib/dialogs/forecast_information_dialog.dart +++ b/lib/dialogs/forecast_information_dialog.dart @@ -1,5 +1,8 @@ -import 'package:chabo/extensions/color_scheme_extension.dart'; +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/helpers/custom_page_routes.dart'; +import 'package:chabo/helpers/device_helper.dart'; import 'package:chabo/models/abstract_forecast.dart'; +import 'package:chabo/screens/notification_screen/notification_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -13,6 +16,8 @@ class ForecastInformationDialog extends StatelessWidget { @override Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return AlertDialog( insetPadding: const EdgeInsets.symmetric( horizontal: 20, @@ -59,47 +64,82 @@ class ForecastInformationDialog extends StatelessWidget { ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( - 15, + CustomProperties.borderRadius, ), ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.all(20), - child: forecast.getInformationWidget(context), - ), - if (forecast.interferingTimeSlots.isNotEmpty) - Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.warningColor, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular( - 15.0, - ), - bottomRight: Radius.circular( - 15.0, + content: Container( + constraints: DeviceHelper.isMobile(context) + ? DeviceHelper.isPortrait(context) + ? null + : BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.5, + ) + : BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.5), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(20), + child: forecast.getInformationWidget(context), + ), + if (forecast.interferingTimeSlots.isNotEmpty) + Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular( + 15.0, + ), + bottomRight: Radius.circular( + 15.0, + ), ), ), - ), - child: Row( - children: [ - const SizedBox(width: 10), - Flexible( - child: Text( - AppLocalizations.of(context)! - .favoriteSlotsInterferenceWarning, - overflow: TextOverflow.clip, - style: TextStyle( - color: Theme.of(context).cardColor, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Flexible( + child: Text( + AppLocalizations.of(context)! + .favoriteSlotsInterferenceWarning, + overflow: TextOverflow.clip, + style: TextStyle( + color: Theme.of(context).cardColor, + ), + ), ), - ), + ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + colorScheme.secondaryContainer, + ), + foregroundColor: MaterialStateProperty.all( + colorScheme.onSecondaryContainer, + ), + ), + onPressed: () => { + Navigator.of(context).pop(), + Navigator.of(context).push( + BottomToTopPageRoute( + builder: (context) => const NotificationScreen( + highlightTimeSlots: true, + ), + ), + ), + }, + child: const Icon( + Icons.notifications_active, + ), + ), + ], ), - ], + ), ), - ), - ], + ], + ), ), ); } diff --git a/lib/dialogs/time_slot_dialog.dart b/lib/dialogs/time_slot_dialog.dart index dbfe8c6d..fdefc20a 100644 --- a/lib/dialogs/time_slot_dialog.dart +++ b/lib/dialogs/time_slot_dialog.dart @@ -1,4 +1,5 @@ import 'package:chabo/bloc/notification/notification_bloc.dart'; +import 'package:chabo/custom_properties.dart'; import 'package:chabo/models/time_slot.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -17,7 +18,7 @@ class TimeSlotDialog extends StatelessWidget { contentPadding: const EdgeInsets.all(15), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( - 15, + CustomProperties.borderRadius, ), ), content: BlocBuilder( diff --git a/lib/extensions/date_time_extension.dart b/lib/extensions/date_time_extension.dart index 70f19b77..c4a8831d 100644 --- a/lib/extensions/date_time_extension.dart +++ b/lib/extensions/date_time_extension.dart @@ -3,7 +3,11 @@ import 'package:flutter/material.dart'; extension DateTimeExtension on DateTime { DateTime previous(int day) { return day == weekday - ? subtract(const Duration(days: 7)) + ? subtract(Duration( + days: 7, + hours: hour, + minutes: minute, + )) : subtract( Duration( days: (weekday - day) % DateTime.daysPerWeek, diff --git a/lib/helpers/custom_page_routes.dart b/lib/helpers/custom_page_routes.dart new file mode 100644 index 00000000..4b7483e7 --- /dev/null +++ b/lib/helpers/custom_page_routes.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +//ignore: prefer-match-file-name +class BottomToTopPageRoute extends MaterialPageRoute { + BottomToTopPageRoute({ + required WidgetBuilder builder, + RouteSettings? settings, + }) : super(builder: builder, settings: settings); + + @override + Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + var begin = const Offset(0.0, 1.0); + var end = Offset.zero; + var curve = Curves.ease; + + var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + } +} diff --git a/lib/helpers/device_helper.dart b/lib/helpers/device_helper.dart new file mode 100644 index 00000000..352cea29 --- /dev/null +++ b/lib/helpers/device_helper.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class DeviceHelper { + static computePreferredOrientation(BuildContext context) { + if (isMobile(context)) { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.portraitUp, + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + } else { + SystemChrome.setPreferredOrientations([ + DeviceOrientation.landscapeLeft, + DeviceOrientation.landscapeRight, + ]); + } + } + + static isPortrait(BuildContext context) { + return MediaQuery.of(context).orientation == Orientation.portrait; + } + + static isMobile(BuildContext context) { + var shortestSide = MediaQuery.of(context).size.shortestSide; + + return shortestSide < 620; + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cc14bdac..815921f6 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -3,16 +3,16 @@ "and": "and", "circulationClosing": "closing", "circulationReOpening": "re opening", - "closed": "closed", + "closed": "closed to traffic", "daySmall": "d", "goodAfternoon": "good afternoon", "goodEvening": "good evening", "goodMorning": "good morning", "nextClosingScheduled": "next closing scheduled in", - "open": "open", + "open": "open to traffic", "scheduledToOpen": "scheduled to open in", "theBridgeIsCurrently": "the Chaban bridge is", - "aboutToClose": "about to close", + "aboutToClose": "about to close to traffic", "settingsTitle": "Settings", "notificationsTitle": "Notifications", "information": "Information", @@ -30,7 +30,6 @@ "errorScreenContentError": "Error", "errorScreenContentMessage": "An error occurred while opening this page. Please find technical information below", "errorScreenContentTechnical_Info": "Technical information", - "lisOfUpcomingClosures": "List of upcoming closures", "unableAppInfo": "Unable to get application information", "appDescription": "The Mobile app to get the closing and opening schedules of the Chaban Delmas bridge located in Bordeaux, France", "changelog": "Changelog", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index cbe7c664..a74917b8 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -3,16 +3,16 @@ "and": "y", "circulationClosing": "cerrado", "circulationReOpening": "reapertura", - "closed": "cerrado", + "closed": "cerrado al tráfico", "daySmall": "d", "goodAfternoon": "buenas tardes", "goodEvening": "buenas noches", "goodMorning": "buenos días", "nextClosingScheduled": "próximo cierre programado en", - "open": "abierto", + "open": "abierto al tráfico", "scheduledToOpen": "programado para abrir en", "theBridgeIsCurrently": "el puente Chaban está", - "aboutToClose": "a punto de cerrarse", + "aboutToClose": "a punto de cerrarse al tráfico", "settingsTitle": "Ajustes", "notificationsTitle": "Notificaciónes", "information": "Información", @@ -30,7 +30,6 @@ "errorScreenContentError": "Error", "errorScreenContentMessage": "Se produjo un error al abrir esta página. Encuentre la información técnica a continuación", "errorScreenContentTechnical_Info": "Información técnica", - "lisOfUpcomingClosures": "Lista de próximos cierres", "unableAppInfo": "No se puede obtener información de la aplicación", "appDescription": "La aplicación móvil para obtener los horarios de cierre y apertura del puente Chaban Delmas ubicado en Bordeaux, Francia", "changelog": "Registro de cambios", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 7312e65d..b1622ea3 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -3,16 +3,16 @@ "and": "et", "circulationClosing": "fermeture", "circulationReOpening": "réouverture", - "closed": "fermé", + "closed": "fermé à la circulation", "daySmall": "j", "goodAfternoon": "bonne après-midi", "goodEvening": "bonsoir", "goodMorning": "bonjour", "nextClosingScheduled": "prochaine fermeture prévue dans", - "open": "ouvert", + "open": "ouvert à la circulation", "scheduledToOpen": "ouverture prévue dans", "theBridgeIsCurrently": "le pont Chaban est", - "aboutToClose": "sur le point de fermer", + "aboutToClose": "sur le point de fermer à la circulation", "settingsTitle": "Paramètres", "notificationsTitle": "Notifications", "information": "Information", @@ -30,7 +30,6 @@ "errorScreenContentError": "Erreur", "errorScreenContentMessage": "Une erreur est survenue lors de l'ouverture de cette page. Veuillez trouver les informations techniques ci-dessous", "errorScreenContentTechnical_Info": "Informations techniques", - "lisOfUpcomingClosures": "Liste des prochaines perturbations", "unableAppInfo": "Impossible d'obtenir les informations de l'application", "appDescription": "L'application Mobile pour connaître les horaires de fermeture et d'ouverture du pont Chaban Delmas situé à Bordeaux, France", "changelog": "Journal des modifications", diff --git a/lib/models/abstract_forecast.dart b/lib/models/abstract_forecast.dart index f9acb8d8..3674970d 100644 --- a/lib/models/abstract_forecast.dart +++ b/lib/models/abstract_forecast.dart @@ -82,7 +82,7 @@ abstract class AbstractForecast extends Equatable { void computeSlotInterference(List timeSlots) { interferingTimeSlots.clear(); for (var timeSlot in timeSlots) { - if (isOverlappingWithPeriod(timeSlot.from, timeSlot.to)) { + if (isOverlappingWithTimeSlot(timeSlot)) { interferingTimeSlots.add(timeSlot); } } @@ -92,14 +92,18 @@ abstract class AbstractForecast extends Equatable { return isOverlappingWith(DateTime.now()); } + bool hasPassed() { + return circulationReOpeningDate.isBefore(DateTime.now()); + } + bool isOverlappingWith(DateTime dateTime) { return dateTime.isAfter(circulationClosingDate) && dateTime.isBefore(circulationReOpeningDate); } - bool isOverlappingWithPeriod(TimeOfDay start, TimeOfDay end) { - final startDateTime = circulationClosingDate.applied(start); - final endDateTime = circulationClosingDate.applied(end); + bool _isOverlapping(DateTime dateTimeToCompare, TimeSlot timeSlot) { + final startDateTime = dateTimeToCompare.applied(timeSlot.from); + final endDateTime = dateTimeToCompare.applied(timeSlot.to); final startIsBeforeClosing = startDateTime.isBefore( circulationClosingDate, @@ -120,10 +124,6 @@ abstract class AbstractForecast extends Equatable { startIsBeforeReopening && !endIsBeforeClosing && endIsBeforeReopening) || - (!startIsBeforeClosing && - startIsBeforeReopening && - endIsBeforeClosing && - !endIsBeforeClosing) || (!startIsBeforeClosing && startIsBeforeReopening && !endIsBeforeClosing && @@ -131,7 +131,20 @@ abstract class AbstractForecast extends Equatable { (startIsBeforeClosing && startIsBeforeReopening && !endIsBeforeClosing && - !endIsBeforeReopening); + !endIsBeforeReopening) || + (!startIsBeforeClosing && + startIsBeforeReopening && + !endIsBeforeClosing && + endIsBeforeReopening); + } + + bool isOverlappingWithTimeSlot(TimeSlot timeSlot) { + /// We must compute the overlapping for the open and closing date separately + /// if open and closing dates are not during the same day + return circulationClosingDate.day != circulationReOpeningDate.day + ? _isOverlapping(circulationClosingDate, timeSlot) || + _isOverlapping(circulationReOpeningDate, timeSlot) + : _isOverlapping(circulationClosingDate, timeSlot); } static bool getBooleanTotalClosingValue(String stringValue) { diff --git a/lib/screens/forecast_screen.dart b/lib/screens/forecast_screen.dart index e4ddb0da..e7090a42 100644 --- a/lib/screens/forecast_screen.dart +++ b/lib/screens/forecast_screen.dart @@ -3,13 +3,15 @@ import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/bloc/status/status_bloc.dart'; import 'package:chabo/cubits/floating_actions_cubit.dart'; +import 'package:chabo/custom_properties.dart'; import 'package:chabo/custom_widget_state.dart'; +import 'package:chabo/helpers/device_helper.dart'; import 'package:chabo/misc/no_scaling_animation.dart'; import 'package:chabo/screens/error_screen.dart'; import 'package:chabo/widgets/ad_banner_widget.dart'; import 'package:chabo/widgets/floating_actions/floating_actions_widget.dart'; import 'package:chabo/widgets/forecast/forecast_list_widget.dart'; -import 'package:chabo/widgets/forecast/status_widget.dart'; +import 'package:chabo/widgets/forecast/status_widget/status_widget.dart'; import 'package:chabo/widgets/progress_indicator/custom_circular_progress_indicator.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -35,14 +37,20 @@ class _ForecastScreenState extends CustomWidgetState { ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ - const Expanded(child: FloatingActionsWidget()), - Padding( - padding: EdgeInsets.only( - left: state.isRightHanded ? 32 : 0, - right: state.isRightHanded ? 0 : 32, - top: 15, - ), - child: const AdBannerWidget(), + const Expanded( + child: FloatingActionsWidget(), + ), + Row( + children: [ + Padding( + padding: EdgeInsets.only( + left: state.isRightHanded ? 32 : 0, + right: state.isRightHanded ? 0 : 32, + top: 5, + ), + child: const AdBannerWidget(), + ), + ], ), ], ), @@ -51,10 +59,8 @@ class _ForecastScreenState extends CustomWidgetState { : FloatingActionButtonLocation.startFloat, floatingActionButtonAnimator: NoScalingAnimation(), body: SafeArea( + top: false, child: BlocBuilder( - buildWhen: (previous, current) => - previous.status == ForecastStatus.initial && - current.status == ForecastStatus.success, builder: (context, state) { switch (state.status) { case ForecastStatus.failure: @@ -68,12 +74,15 @@ class _ForecastScreenState extends CustomWidgetState { listeners: [ BlocListener( listener: (context, state) { + /// If the ForecastState changes, update the previous and current forecasts BlocProvider.of(context).add( StatusChanged( currentForecast: state.currentForecast, previousForecast: state.previousForecast, ), ); + + /// And scroll to the new one BlocProvider.of(context).add( GoTo(goTo: state.currentForecast), ); @@ -81,11 +90,14 @@ class _ForecastScreenState extends CustomWidgetState { ), BlocListener( listener: (context, state) { + /// If the NotificationState changes, update the durationNotificationValue to get the right color of the current status widget BlocProvider.of(context).add( StatusDurationChanged( duration: state.durationNotificationValue, ), ); + + /// And compute all notifications BlocProvider.of(context).add( ComputeNotificationEvent( forecasts: BlocProvider.of( @@ -97,14 +109,115 @@ class _ForecastScreenState extends CustomWidgetState { }, ), ], - child: Column( - children: const [ - StatusWidget(), - Expanded( - flex: 11, - child: ForecastListWidget(), - ), - ], + child: NotificationListener( + onNotification: (scrollNotification) { + if (scrollNotification is! UserScrollNotification) { + BlocProvider.of(context).add( + ScrollStatusChanged(), + ); + + /// Scroll reach the top and display the large layout for the status widget + if (scrollNotification.metrics.pixels <= 100) { + BlocProvider.of(context) + .add(StatusWidgetDimensionChanged( + context: context, + dimension: StatusWidgetDimension.large, + )); + } else { + /// Else, display the small one + BlocProvider.of(context) + .add(StatusWidgetDimensionChanged( + context: context, + dimension: StatusWidgetDimension.small, + )); + } + } + + return true; + }, + child: BlocBuilder( + builder: (context, state) { + return DeviceHelper.isPortrait(context) + ? CustomScrollView( + physics: const BouncingScrollPhysics(), + slivers: [ + SliverAppBar( + pinned: true, + snap: false, + stretch: true, + collapsedHeight: 250, + expandedHeight: 250, + shadowColor: Colors.black, + backgroundColor: Theme.of(context) + .colorScheme + .surfaceVariant, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + bottom: Radius.circular( + CustomProperties.borderRadius * 2, + ), + ), + ), + flexibleSpace: const FlexibleSpaceBar( + titlePadding: EdgeInsets.zero, + centerTitle: true, + expandedTitleScale: 1, + title: StatusWidget(), + ), + ), + const ForecastListWidget(), + ], + ) + : Row( + children: [ + Flexible( + flex: !DeviceHelper.isMobile(context) + ? 2 + : 1, + child: Column( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .surfaceVariant, + borderRadius: + const BorderRadius.vertical( + bottom: Radius.circular( + CustomProperties + .borderRadius * + 2, + ), + ), + ), + constraints: BoxConstraints( + minHeight: + DeviceHelper.isMobile( + context, + ) + ? 270 + : 380, + ), + child: const StatusWidget(), + ), + const SizedBox( + height: 50, + ), + ], + ), + ), + const Flexible( + child: CustomScrollView( + physics: BouncingScrollPhysics(), + slivers: [ + ForecastListWidget(), + ], + ), + ), + ], + ); + }, + ), ), ); default: diff --git a/lib/screens/notification_screen/custom_list_tile_widget.dart b/lib/screens/notification_screen/custom_list_tile_widget.dart new file mode 100644 index 00000000..a651ff9d --- /dev/null +++ b/lib/screens/notification_screen/custom_list_tile_widget.dart @@ -0,0 +1,95 @@ +part of 'notification_screen.dart'; + +class _CustomListTileWidget extends StatelessWidget { + final bool enabled; + final bool constrainedBySlots; + final Function()? onTap; + final Function(bool) onChanged; + final String title; + final String subtitle; + final IconData leadingIcon; + final Color? iconColor; + + const _CustomListTileWidget({ + Key? key, + required this.enabled, + this.onTap, + this.iconColor, + required this.title, + required this.subtitle, + required this.leadingIcon, + required this.onChanged, + required this.constrainedBySlots, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ListTile( + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Flexible( + flex: 3, + child: Text( + title, + style: Theme.of(context).textTheme.titleLarge, + overflow: TextOverflow.clip, + ), + ), + const SizedBox( + width: 10, + ), + Flexible( + child: AnimatedSwitcher( + duration: const Duration( + milliseconds: CustomProperties.animationDurationMs, + ), + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: + CurvedAnimation(parent: animation, curve: Curves.easeIn), + child: SlideTransition( + position: Tween( + begin: const Offset(-1.0, 0.0), + end: const Offset(0.0, 0.0), + ).animate(animation), + child: child, + ), + ); + }, + child: constrainedBySlots && enabled + ? CircleAvatar( + radius: 5, + backgroundColor: + Theme.of(context).colorScheme.warningColor, + child: Container(), + ) + : const SizedBox(), + ), + ), + ], + ), + subtitle: Text(subtitle), + leading: Icon( + leadingIcon, + ), + onTap: onTap, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + onTap != null + ? const VerticalDivider( + width: 20, + ) + : const SizedBox.shrink(), + Switch.adaptive( + value: enabled, + onChanged: onChanged, + ), + ], + ), + iconColor: iconColor ?? Theme.of(context).colorScheme.primary, + enabled: enabled, + ); + } +} diff --git a/lib/screens/notification_screen/favorite_slots_widget.dart b/lib/screens/notification_screen/favorite_slots_widget.dart new file mode 100644 index 00000000..f0d40bbe --- /dev/null +++ b/lib/screens/notification_screen/favorite_slots_widget.dart @@ -0,0 +1,131 @@ +part of 'notification_screen.dart'; + +class _FavoriteSlotsWidget extends StatefulWidget { + final NotificationState notificationState; + final bool highlightTimeSlots; + + const _FavoriteSlotsWidget({ + Key? key, + required this.notificationState, + required this.highlightTimeSlots, + }) : super(key: key); + + @override + State createState() { + return _FavoriteSlotsWidgetState(); + } +} + +class _FavoriteSlotsWidgetState extends State<_FavoriteSlotsWidget> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _animation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + )..repeat(reverse: true); + Future.delayed(const Duration(milliseconds: 2000), () { + if (mounted) { + _controller.stop(); + _controller.forward(); + } + }); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + _animation = ColorTween( + begin: Theme.of(context).colorScheme.primary, + end: Theme.of(context).colorScheme.background, + ).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeInQuad, + ), + )..addListener( + () { + /// Trigger le rebuild of the widget + // ignore: no-empty-block + setState(() {}); + }, + ); + + return Column( + children: [ + _CustomListTileWidget( + onChanged: (bool value) => { + BlocProvider.of(context).add( + EnabledTimeSlotEvent( + enabled: value, + ), + ), + if (value) + { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + duration: const Duration(seconds: 7), + showCloseIcon: true, + backgroundColor: Theme.of(context).colorScheme.warningColor, + content: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: Text( + AppLocalizations.of(context)! + .favoriteTimeSlotEnabledWarning, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ), + } + else + { + ScaffoldMessenger.of(context).hideCurrentSnackBar( + reason: SnackBarClosedReason.action, + ), + }, + }, + enabled: widget.notificationState.timeSlotsEnabledForNotifications, + title: AppLocalizations.of(context)!.favoriteSlots, + subtitle: AppLocalizations.of(context)!.favoriteSlotsDescription, + leadingIcon: Icons.warning_rounded, + iconColor: Theme.of(context).colorScheme.warningColor, + constrainedBySlots: false, + ), + Container( + color: + widget.highlightTimeSlots ? _animation.value : Colors.transparent, + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + for (var i = 0; + i < widget.notificationState.timeSlotsValue.length; + i++) ...[ + TimeSlotWidget( + timeSlot: widget.notificationState.timeSlotsValue[i], + index: i, + ), + ], + ], + ), + ), + ], + ); + } +} diff --git a/lib/screens/notification_screen.dart b/lib/screens/notification_screen/notification_screen.dart similarity index 70% rename from lib/screens/notification_screen.dart rename to lib/screens/notification_screen/notification_screen.dart index 4f39bfc2..bdfdba7b 100644 --- a/lib/screens/notification_screen.dart +++ b/lib/screens/notification_screen/notification_screen.dart @@ -15,8 +15,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +part 'custom_list_tile_widget.dart'; + +part 'favorite_slots_widget.dart'; + class NotificationScreen extends StatefulWidget { - const NotificationScreen({Key? key}) : super(key: key); + final bool highlightTimeSlots; + + const NotificationScreen({ + Key? key, + required this.highlightTimeSlots, + }) : super(key: key); @override State createState() => _NotificationScreenState(); @@ -60,6 +69,7 @@ class _NotificationScreenState extends CustomWidgetState { body: SingleChildScrollView( padding: const EdgeInsets.only( top: 20, + bottom: 100, ), child: BlocBuilder( builder: (context, notificationState) { @@ -115,72 +125,9 @@ class _NotificationScreenState extends CustomWidgetState { ), Column( children: [ - _CustomListTile( - onChanged: (bool value) => { - BlocProvider.of(context).add( - EnabledTimeSlotEvent( - enabled: value, - ), - ), - if (value) - { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - duration: const Duration(seconds: 7), - showCloseIcon: true, - backgroundColor: Theme.of(context) - .colorScheme - .warningColor, - content: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Text( - AppLocalizations.of(context)! - .favoriteTimeSlotEnabledWarning, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - ), - } - else - { - ScaffoldMessenger.of(context) - .hideCurrentSnackBar( - reason: SnackBarClosedReason.action, - ), - }, - }, - enabled: notificationState - .timeSlotsEnabledForNotifications, - title: AppLocalizations.of(context)!.favoriteSlots, - subtitle: AppLocalizations.of(context)! - .favoriteSlotsDescription, - leadingIcon: Icons.warning_rounded, - iconColor: Theme.of(context).colorScheme.warningColor, - constrainedBySlots: false, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - for (var i = 0; - i < notificationState.timeSlotsValue.length; - i++) ...[ - TimeSlotWidget( - timeSlot: notificationState.timeSlotsValue[i], - index: i, - ), - ], - ], - ), + _FavoriteSlotsWidget( + highlightTimeSlots: widget.highlightTimeSlots, + notificationState: notificationState, ), ], ), @@ -195,7 +142,7 @@ class _NotificationScreenState extends CustomWidgetState { const SizedBox( height: 10, ), - _CustomListTile( + _CustomListTileWidget( onChanged: (bool value) => BlocProvider.of(context).add( OpeningNotificationStateEvent( @@ -212,7 +159,7 @@ class _NotificationScreenState extends CustomWidgetState { constrainedBySlots: notificationState.timeSlotsEnabledForNotifications, ), - _CustomListTile( + _CustomListTileWidget( onChanged: (bool value) => BlocProvider.of(context).add( ClosingNotificationStateEvent( @@ -232,7 +179,7 @@ class _NotificationScreenState extends CustomWidgetState { const SizedBox( height: 20, ), - _CustomListTile( + _CustomListTileWidget( onTap: () { showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, @@ -285,7 +232,7 @@ class _NotificationScreenState extends CustomWidgetState { constrainedBySlots: notificationState.timeSlotsEnabledForNotifications, ), - _CustomListTile( + _CustomListTileWidget( onTap: () { showTimePicker( initialEntryMode: TimePickerEntryMode.dialOnly, @@ -334,7 +281,7 @@ class _NotificationScreenState extends CustomWidgetState { constrainedBySlots: notificationState.timeSlotsEnabledForNotifications, ), - _CustomListTile( + _CustomListTileWidget( onTap: () { showDialog( context: context, @@ -395,96 +342,3 @@ class _NotificationScreenState extends CustomWidgetState { ); } } - -class _CustomListTile extends StatelessWidget { - final bool enabled; - final bool constrainedBySlots; - final Function()? onTap; - final Function(bool) onChanged; - final String title; - final String subtitle; - final IconData leadingIcon; - final Color? iconColor; - - const _CustomListTile({ - Key? key, - required this.enabled, - this.onTap, - this.iconColor, - required this.title, - required this.subtitle, - required this.leadingIcon, - required this.onChanged, - required this.constrainedBySlots, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return ListTile( - title: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Flexible( - flex: 3, - child: Text( - title, - style: Theme.of(context).textTheme.titleLarge, - overflow: TextOverflow.clip, - ), - ), - const SizedBox( - width: 10, - ), - Flexible( - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 200), - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: - CurvedAnimation(parent: animation, curve: Curves.easeIn), - child: SlideTransition( - position: Tween( - begin: const Offset(-1.0, 0.0), - end: const Offset(0.0, 0.0), - ).animate(animation), - child: child, - ), - ); - }, - child: constrainedBySlots && enabled - ? CircleAvatar( - radius: 5, - backgroundColor: - Theme.of(context).colorScheme.warningColor, - child: Container(), - ) - : const SizedBox(), - ), - ), - ], - ), - horizontalTitleGap: 0, - subtitle: Text(subtitle), - leading: Icon( - leadingIcon, - ), - onTap: onTap, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - onTap != null - ? const VerticalDivider( - width: 20, - ) - : const SizedBox.shrink(), - Switch.adaptive( - value: enabled, - onChanged: onChanged, - ), - ], - ), - iconColor: iconColor ?? Theme.of(context).colorScheme.primary, - enabled: enabled, - ); - } -} diff --git a/lib/service/storage_service.dart b/lib/service/storage_service.dart index 8e5e0214..ffe9ce89 100644 --- a/lib/service/storage_service.dart +++ b/lib/service/storage_service.dart @@ -32,12 +32,6 @@ class StorageService { return await sharedPreferences.setString(key, value.inMinutes.toString()); } - Future saveDateTime(String key, DateTime value) async { - developer.log('{$key: $value}', name: 'storage-service.on.saveDuration'); - - return await sharedPreferences.setString(key, value.toString()); - } - Future saveTimeOfDay(String key, TimeOfDay value) async { developer.log('{$key: $value}', name: 'storage-service.on.saveTimeOfDay'); diff --git a/lib/widgets/ad_banner_widget.dart b/lib/widgets/ad_banner_widget.dart index 3822e6da..754e126d 100644 --- a/lib/widgets/ad_banner_widget.dart +++ b/lib/widgets/ad_banner_widget.dart @@ -1,6 +1,8 @@ import 'dart:developer' as developer; +import 'package:chabo/custom_properties.dart'; import 'package:chabo/helpers/ad_helper.dart'; +import 'package:chabo/helpers/device_helper.dart'; import 'package:flutter/material.dart'; import 'package:google_mobile_ads/google_mobile_ads.dart'; @@ -56,18 +58,30 @@ class _AdBannerWidgetState extends State { @override Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + return _ad != null ? Card( child: Container( key: const ValueKey('adContent'), - height: 55, + constraints: BoxConstraints( + maxHeight: 55, + maxWidth: DeviceHelper.isPortrait(context) + ? screenWidth / 1.11 + //ignore: avoid-nested-conditional-expressions + : !DeviceHelper.isMobile(context) + ? screenWidth / 1.55 + : screenWidth / 2.13, + ), alignment: Alignment.center, child: AnimatedSize( curve: Curves.ease, duration: const Duration(seconds: 1), child: AnimatedSwitcher( duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 500), + reverseDuration: const Duration( + milliseconds: CustomProperties.animationDurationMs, + ), transitionBuilder: (child, animation) { return FadeTransition( opacity: animation, diff --git a/lib/widgets/floating_actions/floating_actions_widget.dart b/lib/widgets/floating_actions/floating_actions_widget.dart index a89555e4..01053c13 100644 --- a/lib/widgets/floating_actions/floating_actions_widget.dart +++ b/lib/widgets/floating_actions/floating_actions_widget.dart @@ -2,8 +2,10 @@ import 'dart:ui'; import 'package:chabo/cubits/floating_actions_cubit.dart'; import 'package:chabo/custom_properties.dart'; -import 'package:chabo/dialogs/chabo_about_dialog.dart'; -import 'package:chabo/screens/notification_screen.dart'; +import 'package:chabo/dialogs/chabo_about_dialog/chabo_about_dialog.dart'; +import 'package:chabo/helpers/custom_page_routes.dart'; +import 'package:chabo/helpers/device_helper.dart'; +import 'package:chabo/screens/notification_screen/notification_screen.dart'; import 'package:chabo/widgets/floating_actions/floating_actions_item.dart'; import 'package:chabo/widgets/theme_switcher_widget.dart'; import 'package:flutter/material.dart'; @@ -34,10 +36,10 @@ class _FloatingActionsWidgetState extends State children: [ AnimatedSwitcher( duration: const Duration( - milliseconds: 200, + milliseconds: CustomProperties.shortAnimationDurationMs, ), reverseDuration: const Duration( - milliseconds: 200, + milliseconds: CustomProperties.shortAnimationDurationMs, ), transitionBuilder: (Widget child, Animation animation) { return FadeTransition( @@ -61,7 +63,10 @@ class _FloatingActionsWidgetState extends State sigmaY: CustomProperties.blurSigmaY, ), child: Wrap( - direction: Axis.vertical, + direction: DeviceHelper.isMobile(context) && + !DeviceHelper.isPortrait(context) + ? Axis.horizontal + : Axis.vertical, crossAxisAlignment: state.isRightHanded ? WrapCrossAlignment.end : WrapCrossAlignment.start, @@ -90,10 +95,18 @@ class _FloatingActionsWidgetState extends State onPressed: () { showModalBottomSheet( useSafeArea: true, + constraints: BoxConstraints( + minWidth: DeviceHelper.isPortrait(context) + ? double.infinity + : MediaQuery.of(context).size.width / 3, + ), + enableDrag: false, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical( - top: Radius.circular(25.0), + top: Radius.circular( + CustomProperties.borderRadius * 2, + ), ), ), builder: (context) { @@ -114,34 +127,13 @@ class _FloatingActionsWidgetState extends State isSpaced: true, ), FloatingActionsItem( - onPressed: () async { + onPressed: () { Navigator.of(context).push( - PageRouteBuilder( - pageBuilder: - (context, animation1, animation2) => - const NotificationScreen(), - transitionsBuilder: ( - context, - animation, - secondaryAnimation, - child, - ) { - const begin = Offset(0.0, 1.0); - const end = Offset.zero; - const curve = Curves.ease; - - var tween = - Tween(begin: begin, end: end).chain( - CurveTween( - curve: curve, - ), - ); - - return SlideTransition( - position: animation.drive(tween), - child: child, - ); - }, + BottomToTopPageRoute( + builder: (context) => + const NotificationScreen( + highlightTimeSlots: false, + ), ), ); context @@ -206,41 +198,19 @@ class _FloatingActionsWidgetState extends State content: [ AnimatedSize( curve: Curves.easeIn, - duration: const Duration(milliseconds: 200), - reverseDuration: const Duration( - milliseconds: 200, + duration: const Duration( + milliseconds: CustomProperties.shortAnimationDurationMs, ), - child: AnimatedSwitcher( - duration: const Duration( - milliseconds: 200, - ), - reverseDuration: const Duration( - milliseconds: 200, - ), - transitionBuilder: - (Widget child, Animation animation) { - return SlideTransition( - position: Tween( - begin: Offset(state.isRightHanded ? .5 : -.5, 0.0), - end: const Offset(0.0, 0.0), - ).animate(animation), - child: FadeTransition( - opacity: CurvedAnimation( - parent: animation, - curve: Curves.easeIn, - ), - child: child, - ), - ); - }, - child: state.isMenuOpen - ? Text( - AppLocalizations.of(context)!.settingsTitle, - style: Theme.of(context).textTheme.titleMedium, - textAlign: TextAlign.start, - ) - : const SizedBox.shrink(), + reverseDuration: const Duration( + milliseconds: 0, ), + child: state.isMenuOpen + ? Text( + AppLocalizations.of(context)!.settingsTitle, + style: Theme.of(context).textTheme.titleMedium, + textAlign: TextAlign.start, + ) + : const SizedBox.shrink(), ), state.isMenuOpen ? const Icon( diff --git a/lib/widgets/forecast/forecast_list_item_widget.dart b/lib/widgets/forecast/forecast_list_item_widget.dart deleted file mode 100644 index 7643782c..00000000 --- a/lib/widgets/forecast/forecast_list_item_widget.dart +++ /dev/null @@ -1,252 +0,0 @@ -import 'dart:ui'; - -import 'package:chabo/custom_properties.dart'; -import 'package:chabo/dialogs/forecast_information_dialog.dart'; -import 'package:chabo/extensions/color_scheme_extension.dart'; -import 'package:chabo/extensions/duration_extension.dart'; -import 'package:chabo/models/abstract_forecast.dart'; -import 'package:chabo/models/time_slot.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; - -class ForecastListItemWidget extends StatelessWidget { - final AbstractForecast forecast; - final Function()? onTap; - final bool hasPassed; - final List timeSlots; - final bool isCurrent; - final int index; - - const ForecastListItemWidget({ - Key? key, - required this.forecast, - required this.index, - required this.hasPassed, - required this.isCurrent, - this.onTap, - required this.timeSlots, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final textTheme = Theme.of(context).textTheme; - - return Padding( - padding: const EdgeInsets.all(5), - child: Stack( - children: [ - ElevatedButton( - style: ButtonStyle( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(CustomProperties.borderRadius), - side: isCurrent - ? BorderSide( - width: 2, - color: forecast.getColor(context, false), - ) - : BorderSide.none, - ), - ), - padding: MaterialStateProperty.all( - const EdgeInsets.only( - right: 0, - ), - ), - ), - onPressed: onTap ?? - () async => { - await showGeneralDialog( - context: context, - pageBuilder: (BuildContext context, _, __) { - return const SizedBox.shrink(); - }, - barrierDismissible: true, - transitionBuilder: (context, a1, a2, widget) { - return FadeTransition( - opacity: - Tween(begin: 0.0, end: 1.0).animate(a1), - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY, - ), - child: ForecastInformationDialog( - forecast: forecast, - ), - ), - ); - }, - barrierLabel: MaterialLocalizations.of(context) - .modalBarrierDismissLabel, - transitionDuration: const Duration( - milliseconds: 300, - ), - ), - }, - child: SizedBox( - height: 60, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 55, - height: 60, - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular( - CustomProperties.borderRadius, - ), - bottomLeft: Radius.circular( - CustomProperties.borderRadius, - ), - ), - color: forecast.getColor( - context, - false, - ), - ), - child: Center( - child: forecast.getIconWidget( - context, - true, - ), - ), - ), - Flexible( - flex: 2, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Icon( - Icons.block_rounded, - size: 18, - color: Colors.red, - ), - Text( - MaterialLocalizations.of(context) - .formatMediumDate( - forecast.circulationClosingDate, - ), - style: textTheme.bodySmall, - ), - ], - ), - Text( - forecast.circulationClosingDateString( - context, - ), - style: textTheme.headlineSmall, - ), - ], - ), - ), - Flexible( - fit: FlexFit.loose, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - forecast.closedDuration.durationToString(context), - style: TextStyle( - color: Theme.of(context).colorScheme.timeColor, - fontWeight: FontWeight.bold, - fontSize: 15, - ), - ), - const Icon( - FontAwesomeIcons.arrowRightLong, - size: 20, - ), - ], - ), - ), - Flexible( - flex: 2, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Icon( - Icons.check_circle, - size: 18, - color: Colors.green, - ), - Text( - MaterialLocalizations.of(context) - .formatMediumDate( - forecast.circulationReOpeningDate, - ), - style: textTheme.bodySmall, - ), - ], - ), - Text( - forecast.circulationReOpeningDateString( - context, - ), - style: textTheme.headlineSmall, - ), - ], - ), - ), - timeSlots.isNotEmpty - ? Container( - width: 30, - height: 60, - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - topRight: Radius.circular( - CustomProperties.borderRadius, - ), - bottomRight: Radius.circular( - CustomProperties.borderRadius, - ), - ), - color: Theme.of(context).colorScheme.warningColor, - ), - child: Icon( - Icons.warning_rounded, - size: 20, - color: Theme.of(context).cardColor, - ), - ) - : Container( - width: 15, - ), - ], - ), - ), - ), - if (hasPassed) - Positioned.fill( - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: CustomProperties.blurSigmaX, - sigmaY: CustomProperties.blurSigmaY, - ), - child: Center( - child: Text( - AppLocalizations.of(context)!.passedClosure, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/widgets/forecast/forecast_list_widget.dart b/lib/widgets/forecast/forecast_list_widget.dart index d825ec22..3d0ef8a6 100644 --- a/lib/widgets/forecast/forecast_list_widget.dart +++ b/lib/widgets/forecast/forecast_list_widget.dart @@ -2,7 +2,7 @@ import 'package:chabo/bloc/forecast/forecast_bloc.dart'; import 'package:chabo/bloc/notification/notification_bloc.dart'; import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; import 'package:chabo/models/abstract_forecast.dart'; -import 'package:chabo/widgets/forecast/forecast_list_item_widget.dart'; +import 'package:chabo/widgets/forecast/forecast_widget/forecast_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; @@ -19,58 +19,51 @@ class ForecastListWidget extends StatefulWidget { class _ForecastListWidgetState extends State { @override Widget build(BuildContext context) { - return NotificationListener( - onNotification: (scrollNotification) { - if (scrollNotification is UserScrollNotification) { - BlocProvider.of(context).add( - ScrollStatusChanged(), - ); - } - - return true; - }, - child: BlocBuilder( - builder: (context, forecastState) { - return BlocBuilder( - buildWhen: (previous, next) => - previous.timeSlotsValue != next.timeSlotsValue, - builder: (context, timeSlotState) { - return ListView.separated( + return BlocBuilder( + builder: (context, forecastState) { + return BlocBuilder( + buildWhen: (previous, next) => + previous.timeSlotsValue != next.timeSlotsValue, + builder: (context, timeSlotState) { + return SliverToBoxAdapter( + child: ListView.separated( + shrinkWrap: true, cacheExtent: 5000, padding: const EdgeInsets.symmetric(horizontal: 5).copyWith( - bottom: 200, + bottom: 150, ), itemBuilder: ( BuildContext context, int index, ) { - forecastState.forecasts[index] + final AbstractForecast forecast = + forecastState.forecasts[index]; + forecast .computeSlotInterference(timeSlotState.timeSlotsValue); - return ForecastListItemWidget( - key: GlobalObjectKey( - forecastState.forecasts[index].hashCode, - ), - isCurrent: forecastState.forecasts[index] == - forecastState.currentForecast, - hasPassed: forecastState - .forecasts[index].circulationReOpeningDate - .isBefore(DateTime.now()), - forecast: forecastState.forecasts[index], - index: index, - timeSlots: - forecastState.forecasts[index].interferingTimeSlots, - ); + return !forecast.hasPassed() + ? ForecastWidget( + key: GlobalObjectKey(forecast.hashCode), + isCurrent: forecast == forecastState.currentForecast, + hasPassed: forecast.hasPassed(), + forecast: forecast, + index: index, + timeSlots: forecast.interferingTimeSlots, + ) + : const SizedBox.shrink(); }, itemCount: forecastState.forecasts.length, controller: BlocProvider.of(context).scrollController, separatorBuilder: (BuildContext context, int index) { - if ((index + 1 <= forecastState.forecasts.length && - forecastState - .forecasts[index].circulationClosingDate.month != - forecastState.forecasts[index + 1] - .circulationClosingDate.month)) { + final AbstractForecast forecast = + forecastState.forecasts[index]; + if ((forecast.circulationClosingDate.month != + forecastState.forecasts[index + 1] + .circulationClosingDate.month) && + !forecast.hasPassed() || + forecastState.forecasts[index + 1] == + forecastState.currentForecast) { return _MonthWidget( forecast: forecastState.forecasts[index + 1], ); @@ -78,11 +71,11 @@ class _ForecastListWidgetState extends State { return const SizedBox.shrink(); }, - ); - }, - ); - }, - ), + ), + ); + }, + ); + }, ); } } diff --git a/lib/widgets/forecast/forecast_widget/closing_info_widget.dart b/lib/widgets/forecast/forecast_widget/closing_info_widget.dart new file mode 100644 index 00000000..3acfa86e --- /dev/null +++ b/lib/widgets/forecast/forecast_widget/closing_info_widget.dart @@ -0,0 +1,41 @@ +part of 'forecast_widget.dart'; + +class _ClosingInfoWidget extends StatelessWidget { + final AbstractForecast forecast; + + const _ClosingInfoWidget({Key? key, required this.forecast}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Icon( + Icons.block_rounded, + size: 18, + color: Colors.red, + ), + Text( + MaterialLocalizations.of(context).formatMediumDate( + forecast.circulationClosingDate, + ), + style: textTheme.bodySmall, + ), + ], + ), + Text( + forecast.circulationClosingDateString( + context, + ), + style: textTheme.headlineSmall, + ), + ], + ); + } +} diff --git a/lib/widgets/forecast/forecast_widget/duration_widget.dart b/lib/widgets/forecast/forecast_widget/duration_widget.dart new file mode 100644 index 00000000..3e8f920a --- /dev/null +++ b/lib/widgets/forecast/forecast_widget/duration_widget.dart @@ -0,0 +1,31 @@ +part of 'forecast_widget.dart'; + +class _DurationWidget extends StatelessWidget { + final AbstractForecast forecast; + + const _DurationWidget({ + Key? key, + required this.forecast, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + forecast.closedDuration.durationToString(context), + style: TextStyle( + color: Theme.of(context).colorScheme.timeColor, + fontWeight: FontWeight.bold, + fontSize: 15, + ), + ), + const Icon( + FontAwesomeIcons.arrowRightLong, + size: 20, + ), + ], + ); + } +} diff --git a/lib/widgets/forecast/forecast_widget/forecast_widget.dart b/lib/widgets/forecast/forecast_widget/forecast_widget.dart new file mode 100644 index 00000000..bf4b410d --- /dev/null +++ b/lib/widgets/forecast/forecast_widget/forecast_widget.dart @@ -0,0 +1,164 @@ +import 'dart:ui'; + +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/dialogs/forecast_information_dialog.dart'; +import 'package:chabo/extensions/color_scheme_extension.dart'; +import 'package:chabo/extensions/duration_extension.dart'; +import 'package:chabo/models/abstract_forecast.dart'; +import 'package:chabo/models/time_slot.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +part 'closing_info_widget.dart'; + +part 'duration_widget.dart'; + +part 'leading_icon_widget.dart'; + +part 'opening_info_widget.dart'; + +part 'time_slot_warning_widget.dart'; + +class ForecastWidget extends StatelessWidget { + final AbstractForecast forecast; + final Function()? onTap; + final bool hasPassed; + final List timeSlots; + final bool isCurrent; + final int index; + final Color? backgroundColor; + + const ForecastWidget({ + Key? key, + required this.forecast, + required this.index, + required this.hasPassed, + required this.isCurrent, + this.onTap, + required this.timeSlots, + this.backgroundColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(5), + child: Stack( + children: [ + ElevatedButton( + style: ButtonStyle( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(CustomProperties.borderRadius), + side: isCurrent + ? BorderSide( + width: 2, + color: backgroundColor ?? + forecast.getColor(context, false), + ) + : BorderSide.none, + ), + ), + padding: MaterialStateProperty.all( + const EdgeInsets.only( + right: 0, + ), + ), + ), + onPressed: onTap ?? + () async => { + await showGeneralDialog( + context: context, + pageBuilder: (BuildContext context, _, __) { + return const SizedBox.shrink(); + }, + barrierDismissible: true, + transitionBuilder: (context, a1, a2, widget) { + return FadeTransition( + opacity: + Tween(begin: 0.0, end: 1.0).animate(a1), + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), + child: ForecastInformationDialog( + forecast: forecast, + ), + ), + ); + }, + barrierLabel: MaterialLocalizations.of(context) + .modalBarrierDismissLabel, + transitionDuration: const Duration( + milliseconds: 300, + ), + ), + }, + child: SizedBox( + height: 60, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _LeadingIconWidget( + forecast: forecast, + backgroundColor: backgroundColor ?? + forecast.getColor( + context, + false, + ), + ), + Flexible( + flex: 2, + child: _ClosingInfoWidget( + forecast: forecast, + ), + ), + Flexible( + fit: FlexFit.loose, + child: _DurationWidget( + forecast: forecast, + ), + ), + Flexible( + flex: 2, + child: _OpeningInfoWidget( + forecast: forecast, + ), + ), + timeSlots.isNotEmpty + ? const _TimeSlotWarningWidget() + : Container( + width: 15, + ), + ], + ), + ), + ), + if (hasPassed) + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: CustomProperties.blurSigmaX, + sigmaY: CustomProperties.blurSigmaY, + ), + child: Center( + child: Text( + AppLocalizations.of(context)!.passedClosure, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/forecast/forecast_widget/leading_icon_widget.dart b/lib/widgets/forecast/forecast_widget/leading_icon_widget.dart new file mode 100644 index 00000000..5823f0df --- /dev/null +++ b/lib/widgets/forecast/forecast_widget/leading_icon_widget.dart @@ -0,0 +1,37 @@ +part of 'forecast_widget.dart'; + +class _LeadingIconWidget extends StatelessWidget { + final AbstractForecast forecast; + final Color? backgroundColor; + + const _LeadingIconWidget({ + Key? key, + required this.forecast, + required this.backgroundColor, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: 55, + height: 60, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular( + CustomProperties.borderRadius, + ), + bottomLeft: Radius.circular( + CustomProperties.borderRadius, + ), + ), + color: backgroundColor, + ), + child: Center( + child: forecast.getIconWidget( + context, + true, + ), + ), + ); + } +} diff --git a/lib/widgets/forecast/forecast_widget/opening_info_widget.dart b/lib/widgets/forecast/forecast_widget/opening_info_widget.dart new file mode 100644 index 00000000..032c53db --- /dev/null +++ b/lib/widgets/forecast/forecast_widget/opening_info_widget.dart @@ -0,0 +1,41 @@ +part of 'forecast_widget.dart'; + +class _OpeningInfoWidget extends StatelessWidget { + final AbstractForecast forecast; + + const _OpeningInfoWidget({Key? key, required this.forecast}) + : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + const Icon( + Icons.check_circle, + size: 18, + color: Colors.green, + ), + Text( + MaterialLocalizations.of(context).formatMediumDate( + forecast.circulationReOpeningDate, + ), + style: textTheme.bodySmall, + ), + ], + ), + Text( + forecast.circulationReOpeningDateString( + context, + ), + style: textTheme.headlineSmall, + ), + ], + ); + } +} diff --git a/lib/widgets/forecast/forecast_widget/time_slot_warning_widget.dart b/lib/widgets/forecast/forecast_widget/time_slot_warning_widget.dart new file mode 100644 index 00000000..bf3853c5 --- /dev/null +++ b/lib/widgets/forecast/forecast_widget/time_slot_warning_widget.dart @@ -0,0 +1,29 @@ +part of 'forecast_widget.dart'; + +class _TimeSlotWarningWidget extends StatelessWidget { + const _TimeSlotWarningWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: 30, + height: 60, + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + topRight: Radius.circular( + CustomProperties.borderRadius, + ), + bottomRight: Radius.circular( + CustomProperties.borderRadius, + ), + ), + color: Theme.of(context).colorScheme.primary, + ), + child: Icon( + Icons.warning_rounded, + size: 20, + color: Theme.of(context).cardColor, + ), + ); + } +} diff --git a/lib/widgets/forecast/status_widget.dart b/lib/widgets/forecast/status_widget.dart deleted file mode 100644 index a58451c7..00000000 --- a/lib/widgets/forecast/status_widget.dart +++ /dev/null @@ -1,224 +0,0 @@ -import 'dart:async'; - -import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; -import 'package:chabo/bloc/status/status_bloc.dart'; -import 'package:chabo/custom_properties.dart'; -import 'package:chabo/custom_widget_state.dart'; -import 'package:chabo/extensions/duration_extension.dart'; -import 'package:chabo/widgets/forecast/forecast_list_item_widget.dart'; -import 'package:chabo/widgets/progress_indicator/custom_circular_progress_indicator.dart'; -import 'package:chabo/widgets/progress_indicator/custom_progress_bar_indicator.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -class StatusWidget extends StatefulWidget { - const StatusWidget({super.key}); - - @override - State createState() { - return StatusWidgetState(); - } -} - -class StatusWidgetState extends CustomWidgetState { - @override - void initState() { - SchedulerBinding.instance.addPostFrameCallback( - (_) { - Timer.periodic( - const Duration(seconds: 1), - (Timer t) => BlocProvider.of(context).add( - StatusRefresh( - context: context, - ), - ), - ); - }, - ); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 800), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: const Duration(milliseconds: 200), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: state.statusLifecycle == StatusLifecycle.empty - ? Padding( - padding: EdgeInsets.symmetric( - vertical: MediaQuery.of(context).size.height / 5, - ), - child: CustomCircularProgressIndicator( - message: AppLocalizations.of(context)!.statusLoadMessage, - ), - ) - : Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 30, - vertical: 10, - ), - child: AnimatedContainer( - duration: const Duration(milliseconds: 500), - padding: const EdgeInsets.all(10), - decoration: BoxDecoration( - color: state.backgroundColor, - borderRadius: const BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, - ), - ), - ), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 500), - transitionBuilder: - (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: Text( - state.mainMessageStatus, - key: ValueKey(state.mainMessageStatus), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 40, - color: state.foregroundColor, - ), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 5.0), - child: Column( - children: [ - Text( - state.timeMessagePrefix, - style: const TextStyle( - fontSize: 20, - ), - ), - !state.durationUntilNextEvent.isNegative - ? Text( - state.durationUntilNextEvent - .durationToString(context), - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), - ) - : const SizedBox.shrink(), - state.completionPercentage != -1 - ? Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20.0, - vertical: 5, - ), - child: SizedBox( - height: 10, - child: ClipRRect( - borderRadius: const BorderRadius.all( - Radius.circular( - CustomProperties.borderRadius, - ), - ), - child: CustomProgressBarIndicator( - max: 1, - current: state.completionPercentage, - color: state.backgroundColor, - ), - ), - ), - ) - : const SizedBox.shrink(), - ], - ), - ), - Flexible( - child: BlocBuilder( - builder: (context, state) { - return AnimatedSize( - curve: Curves.ease, - duration: const Duration(milliseconds: 800), - child: AnimatedSwitcher( - duration: const Duration(seconds: 1), - reverseDuration: - const Duration(milliseconds: 200), - transitionBuilder: (child, animation) { - return FadeTransition( - opacity: animation, - child: child, - ); - }, - child: state.showCurrentStatus && - state.currentTarget != null - ? Padding( - padding: const EdgeInsets.only( - left: 10.0, - right: 10.0, - bottom: 15.0, - ), - child: ForecastListItemWidget( - onTap: () => - BlocProvider.of( - context, - ).add( - GoTo( - goTo: state.currentTarget, - ), - ), - hasPassed: false, - isCurrent: true, - forecast: state.currentTarget!, - index: -1, - timeSlots: const [], - ), - ) - : const SizedBox.shrink(), - ), - ); - }, - ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text( - AppLocalizations.of(context)! - .lisOfUpcomingClosures, - style: const TextStyle( - fontSize: 20, - ), - ), - const Icon(Icons.arrow_circle_down), - ], - ), - ), - ], - ), - ), - ); - }, - ); - } -} diff --git a/lib/widgets/forecast/status_widget/current_status_widget.dart b/lib/widgets/forecast/status_widget/current_status_widget.dart new file mode 100644 index 00000000..02800f11 --- /dev/null +++ b/lib/widgets/forecast/status_widget/current_status_widget.dart @@ -0,0 +1,62 @@ +part of 'status_widget.dart'; + +class _CurrentStatusWidget extends StatelessWidget { + final StatusState statusState; + + const _CurrentStatusWidget({Key? key, required this.statusState}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return AnimatedSize( + curve: Curves.easeOut, + duration: const Duration( + milliseconds: CustomProperties.animationDurationMs, + ), + reverseDuration: const Duration(milliseconds: 0), + child: AnimatedSwitcher( + duration: const Duration( + seconds: 1, + ), + reverseDuration: const Duration(milliseconds: 0), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: state.showCurrentStatus && + state.currentTarget != null && + statusState.statusWidgetDimension == + StatusWidgetDimension.small + ? Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + ), + child: ForecastWidget( + onTap: () => { + BlocProvider.of( + context, + ).add( + GoTo( + goTo: state.currentTarget, + ), + ), + }, + hasPassed: false, + isCurrent: true, + forecast: state.currentTarget!, + index: -1, + timeSlots: const [], + backgroundColor: statusState.backgroundColor, + ), + ) + : const SizedBox.shrink(), + ), + ); + }, + ); + } +} diff --git a/lib/widgets/forecast/status_widget/layout_widget.dart b/lib/widgets/forecast/status_widget/layout_widget.dart new file mode 100644 index 00000000..5422214c --- /dev/null +++ b/lib/widgets/forecast/status_widget/layout_widget.dart @@ -0,0 +1,31 @@ +part of 'status_widget.dart'; + +class _LayoutWidget extends StatelessWidget { + final StatusState statusState; + + const _LayoutWidget({ + Key? key, + required this.statusState, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _TextWidget( + statusState: statusState, + ), + _ProgressIndicatorWidget( + statusState: statusState, + ), + const SizedBox( + height: 10, + ), + _CurrentStatusWidget( + statusState: statusState, + ), + ], + ); + } +} diff --git a/lib/widgets/forecast/status_widget/progress_indicator_widget.dart b/lib/widgets/forecast/status_widget/progress_indicator_widget.dart new file mode 100644 index 00000000..aa58bb66 --- /dev/null +++ b/lib/widgets/forecast/status_widget/progress_indicator_widget.dart @@ -0,0 +1,74 @@ +part of 'status_widget.dart'; + +class _ProgressIndicatorWidget extends StatelessWidget { + final StatusState statusState; + + const _ProgressIndicatorWidget({ + Key? key, + required this.statusState, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + AnimatedSwitcher( + duration: const Duration( + milliseconds: CustomProperties.shortAnimationDurationMs, + ), + reverseDuration: const Duration( + milliseconds: 0, + ), + switchInCurve: Curves.ease, + switchOutCurve: Curves.ease, + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: !(statusState.statusWidgetDimension == + StatusWidgetDimension.small) + ? Text( + statusState.timeMessagePrefix, + style: Theme.of(context).textTheme.labelLarge!.copyWith( + fontSize: 20, + ), + ) + : const SizedBox.shrink(), + ), + !statusState.durationUntilNextEvent.isNegative + ? Text( + statusState.durationUntilNextEvent.durationToString(context), + style: Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ) + : const SizedBox.shrink(), + statusState.completionPercentage != -1 + ? Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20.0, + ), + child: SizedBox( + height: 10, + child: ClipRRect( + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + child: CustomProgressBarIndicator( + max: 1, + current: statusState.completionPercentage, + color: statusState.backgroundColor, + ), + ), + ), + ) + : const SizedBox.shrink(), + ], + ); + } +} diff --git a/lib/widgets/forecast/status_widget/status_widget.dart b/lib/widgets/forecast/status_widget/status_widget.dart new file mode 100644 index 00000000..cedc2281 --- /dev/null +++ b/lib/widgets/forecast/status_widget/status_widget.dart @@ -0,0 +1,80 @@ +import 'dart:async'; + +import 'package:chabo/bloc/scroll_status/scroll_status_bloc.dart'; +import 'package:chabo/bloc/status/status_bloc.dart'; +import 'package:chabo/custom_properties.dart'; +import 'package:chabo/custom_widget_state.dart'; +import 'package:chabo/extensions/duration_extension.dart'; +import 'package:chabo/helpers/device_helper.dart'; +import 'package:chabo/widgets/forecast/forecast_widget/forecast_widget.dart'; +import 'package:chabo/widgets/progress_indicator/custom_circular_progress_indicator.dart'; +import 'package:chabo/widgets/progress_indicator/custom_progress_bar_indicator.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +part 'current_status_widget.dart'; + +part 'layout_widget.dart'; + +part 'progress_indicator_widget.dart'; + +part 'text_widget.dart'; + +class StatusWidget extends StatefulWidget { + const StatusWidget({super.key}); + + @override + State createState() { + return StatusWidgetState(); + } +} + +class StatusWidgetState extends CustomWidgetState { + @override + void initState() { + SchedulerBinding.instance.addPostFrameCallback( + (_) { + if (mounted) { + Timer.periodic( + const Duration(seconds: 1), + (Timer t) => BlocProvider.of(context).add( + StatusRefresh( + context: context, + ), + ), + ); + } + }, + ); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return AnimatedSwitcher( + duration: const Duration(seconds: 1), + reverseDuration: const Duration( + milliseconds: CustomProperties.shortAnimationDurationMs, + ), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: state.statusLifecycle == StatusLifecycle.empty + ? CustomCircularProgressIndicator( + message: AppLocalizations.of(context)!.statusLoadMessage, + ) + : _LayoutWidget( + statusState: state, + ), + ); + }, + ); + } +} diff --git a/lib/widgets/forecast/status_widget/text_widget.dart b/lib/widgets/forecast/status_widget/text_widget.dart new file mode 100644 index 00000000..1ca739d8 --- /dev/null +++ b/lib/widgets/forecast/status_widget/text_widget.dart @@ -0,0 +1,68 @@ +part of 'status_widget.dart'; + +class _TextWidget extends StatelessWidget { + final StatusState statusState; + + const _TextWidget({ + Key? key, + required this.statusState, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 5, + ), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 13, horizontal: 8), + decoration: BoxDecoration( + color: statusState.backgroundColor, + borderRadius: const BorderRadius.all( + Radius.circular( + CustomProperties.borderRadius, + ), + ), + ), + child: AnimatedSize( + curve: Curves.ease, + duration: const Duration( + milliseconds: CustomProperties.shortAnimationDurationMs, + ), + child: AnimatedSwitcher( + duration: const Duration( + milliseconds: CustomProperties.shortAnimationDurationMs, + ), + reverseDuration: const Duration( + milliseconds: 0, + ), + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + child: Text( + key: ValueKey( + statusState.statusWidgetDimension.toString(), + ), + statusState.mainMessageStatus, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + color: statusState.foregroundColor, + fontWeight: FontWeight.bold, + fontSize: DeviceHelper.isMobile(context) + ? 30 + : DeviceHelper.isPortrait(context) + ? 30 + : 55, + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/progress_indicator/custom_circular_progress_indicator.dart b/lib/widgets/progress_indicator/custom_circular_progress_indicator.dart index 23cf3277..b8621cf9 100644 --- a/lib/widgets/progress_indicator/custom_circular_progress_indicator.dart +++ b/lib/widgets/progress_indicator/custom_circular_progress_indicator.dart @@ -24,9 +24,9 @@ class CustomCircularProgressIndicator extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: Text( message, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontWeight: FontWeight.bold, + ), ), ), ], diff --git a/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart b/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart index 4bd6ede0..67cc1b6c 100644 --- a/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart +++ b/lib/widgets/progress_indicator/custom_progress_bar_indicator.dart @@ -1,3 +1,4 @@ +import 'package:chabo/custom_properties.dart'; import 'package:flutter/material.dart'; class CustomProgressBarIndicator extends StatelessWidget { @@ -23,7 +24,9 @@ class CustomProgressBarIndicator extends StatelessWidget { alignment: Alignment.centerLeft, children: [ AnimatedContainer( - duration: const Duration(milliseconds: 500), + duration: const Duration( + milliseconds: CustomProperties.animationDurationMs, + ), width: x, height: 15, decoration: BoxDecoration( @@ -35,7 +38,9 @@ class CustomProgressBarIndicator extends StatelessWidget { ), ), AnimatedContainer( - duration: const Duration(milliseconds: 500), + duration: const Duration( + milliseconds: CustomProperties.animationDurationMs, + ), width: percent, height: 10, decoration: BoxDecoration( diff --git a/lib/widgets/theme_switcher_widget.dart b/lib/widgets/theme_switcher_widget.dart index c6aee3b1..ae97a19b 100644 --- a/lib/widgets/theme_switcher_widget.dart +++ b/lib/widgets/theme_switcher_widget.dart @@ -1,5 +1,6 @@ import 'package:animated_toggle_switch/animated_toggle_switch.dart'; import 'package:chabo/bloc/theme/theme_bloc.dart'; +import 'package:chabo/custom_properties.dart'; import 'package:chabo/models/enums/theme_state_status.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -55,10 +56,10 @@ class ThemeSwitcherWidget extends StatelessWidget { ), AnimatedSwitcher( duration: const Duration( - milliseconds: 200, + milliseconds: CustomProperties.shortAnimationDurationMs, ), reverseDuration: const Duration( - milliseconds: 200, + milliseconds: CustomProperties.shortAnimationDurationMs, ), transitionBuilder: (Widget child, Animation animation) { return SlideTransition( diff --git a/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus b/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus index 2fb5e920..5ea7677c 120000 --- a/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus +++ b/linux/flutter/ephemeral/.plugin_symlinks/package_info_plus @@ -1 +1 @@ -C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/package_info_plus-3.0.3/ \ No newline at end of file +C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/package_info_plus-4.0.0/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux b/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux index 3d69ff4a..83625431 120000 --- a/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux +++ b/linux/flutter/ephemeral/.plugin_symlinks/shared_preferences_linux @@ -1 +1 @@ -C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/shared_preferences_linux-2.1.5/ \ No newline at end of file +C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/shared_preferences_linux-2.2.0/ \ No newline at end of file diff --git a/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux b/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux index a273bc61..c2842472 120000 --- a/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux +++ b/linux/flutter/ephemeral/.plugin_symlinks/url_launcher_linux @@ -1 +1 @@ -C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_linux-3.0.4/ \ No newline at end of file +C:/Users/Valentin/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_linux-3.0.5/ \ No newline at end of file diff --git a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig index 57995472..5dc813a2 100644 --- a/macos/Flutter/ephemeral/Flutter-Generated.xcconfig +++ b/macos/Flutter/ephemeral/Flutter-Generated.xcconfig @@ -3,8 +3,8 @@ FLUTTER_ROOT=D:\src\Flutter FLUTTER_APPLICATION_PATH=D:\Code\chabo COCOAPODS_PARALLEL_CODE_SIGN=true FLUTTER_BUILD_DIR=build -FLUTTER_BUILD_NAME=1.6.0 -FLUTTER_BUILD_NUMBER=1.6.0 +FLUTTER_BUILD_NAME=1.6.1 +FLUTTER_BUILD_NUMBER=1.6.1 DART_OBFUSCATION=false TRACK_WIDGET_CREATION=true TREE_SHAKE_ICONS=false diff --git a/macos/Flutter/ephemeral/flutter_export_environment.sh b/macos/Flutter/ephemeral/flutter_export_environment.sh index eb328ef2..6c50bb77 100644 --- a/macos/Flutter/ephemeral/flutter_export_environment.sh +++ b/macos/Flutter/ephemeral/flutter_export_environment.sh @@ -4,8 +4,8 @@ export "FLUTTER_ROOT=D:\src\Flutter" export "FLUTTER_APPLICATION_PATH=D:\Code\chabo" export "COCOAPODS_PARALLEL_CODE_SIGN=true" export "FLUTTER_BUILD_DIR=build" -export "FLUTTER_BUILD_NAME=1.6.0" -export "FLUTTER_BUILD_NUMBER=1.6.0" +export "FLUTTER_BUILD_NAME=1.6.1" +export "FLUTTER_BUILD_NUMBER=1.6.1" export "DART_OBFUSCATION=false" export "TRACK_WIDGET_CREATION=true" export "TREE_SHAKE_ICONS=false" diff --git a/pubspec.lock b/pubspec.lock index bca39eb5..20dae7db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,26 +53,26 @@ packages: dependency: transitive description: name: archive - sha256: d6347d54a2d8028e0437e3c099f66fdb8ae02c4720c1e7534c9f24c10351f85d + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" url: "https://pub.dev" source: hosted - version: "3.3.6" + version: "3.3.7" args: dependency: transitive description: name: args - sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + sha256: c372bb384f273f0c2a8aaaa226dad84dc27c8519a691b888725dec59518ad53a url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" async: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" bloc: dependency: transitive description: @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1" + bloc_test: + dependency: "direct dev" + description: + name: bloc_test + sha256: ffbb60c17ee3d8e3784cb78071088e353199057233665541e8ac6cd438dca8ad + url: "https://pub.dev" + source: hosted + version: "9.1.1" boolean_selector: dependency: transitive description: @@ -97,22 +105,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "43865b79fbb78532e4bff7c33087aa43b1d488c4fdef014eaef568af6d8016dc" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "2f17434bd5d52a26762043d6b43bb53b3acd029b4d9071a329f46d67ef297e6d" + url: "https://pub.dev" + source: hosted + version: "8.5.0" characters: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" cli_util: dependency: transitive description: @@ -129,14 +161,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + url: "https://pub.dev" + source: hosted + version: "4.4.0" collection: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" convert: dependency: transitive description: @@ -145,14 +185,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" + source: hosted + version: "1.6.3" crypto: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" csslib: dependency: transitive description: @@ -201,6 +249,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.8" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" enum_to_string: dependency: "direct main" description: @@ -229,10 +285,10 @@ packages: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" file: dependency: "direct main" description: @@ -241,6 +297,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -258,10 +322,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "8546a9b9510e1a260b8d55fb2d07096e8a8552c6a2c2bf529100344894b2b41a" + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -274,26 +338,26 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "2876372952b65ca7f684e698eba22bda1cf581fa071dd30ba2f01900f507d0d1" + sha256: ee6ee56855aa920899b68586b538474d086c149932220b47b92502cbfb5ba5e5 url: "https://pub.dev" source: hosted - version: "14.0.0+1" + version: "14.0.0+2" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "909bb95de05a2e793503a2437146285a2f600cd0b3f826e26b870a334d8586d7" + sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.0+1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "63235c42de5b6c99846969a27ad0209c401e6b77b0498939813725b5791c107c" + sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.0+1" flutter_localizations: dependency: "direct main" description: flutter @@ -325,6 +389,14 @@ packages: url: "https://pub.dev" source: hosted version: "10.4.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" glob: dependency: transitive description: @@ -337,10 +409,10 @@ packages: dependency: "direct main" description: name: google_fonts - sha256: "927573f2e8a8d65c17931e21918ad0ab0666b1b636537de7c4932bdb487b190f" + sha256: "6b6f10f0ce3c42f6552d1c70d2c28d764cf22bb487f50f66cca31dcd5194f4d6" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.4" google_mobile_ads: dependency: "direct main" description: @@ -361,10 +433,18 @@ packages: dependency: "direct main" description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "3.2.1" http_parser: dependency: transitive description: @@ -377,58 +457,74 @@ packages: dependency: transitive description: name: image - sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227" + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf url: "https://pub.dev" source: hosted - version: "4.0.15" + version: "4.0.17" intl: dependency: "direct main" description: name: intl - sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 url: "https://pub.dev" source: hosted - version: "0.17.0" + version: "0.18.0" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" js: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: transitive description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" + source: hosted + version: "1.1.1" markdown: dependency: transitive description: name: markdown - sha256: b3c60dee8c2af50ad0e6e90cceba98e47718a6ee0a7a6772c77846a0cc21f78b + sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.1.0" matcher: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -441,10 +537,34 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: dd61809f04da1838a680926de50a9e87385c1de91c6579629c3d1723946e8059 url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "5.4.0" + mocktail: + dependency: transitive + description: + name: mocktail + sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53" + url: "https://pub.dev" + source: hosted + version: "0.3.0" nested: dependency: transitive description: @@ -453,6 +573,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" package_config: dependency: transitive description: @@ -465,10 +593,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "8df5ab0a481d7dc20c0e63809e90a588e496d276ba53358afc4c4443d0a00697" + sha256: d39e8fbff4c5aef4592737e25ad6ac500df006ce7a7a8e1f838ce1256e167542 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "4.0.0" package_info_plus_platform_interface: dependency: transitive description: @@ -481,34 +609,34 @@ packages: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" path_provider: dependency: transitive description: name: path_provider - sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9" + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.0.15" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.27" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059" + sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.3" path_provider_linux: dependency: transitive description: @@ -529,18 +657,18 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 + sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6 url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" petitparser: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" platform: dependency: transitive description: @@ -561,10 +689,18 @@ packages: dependency: transitive description: name: pointycastle - sha256: "57b6b78df14175658f09c5dfcfc51a46ad9561a3504fe679913dab404d0cc0f2" + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "3.7.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" process: dependency: transitive description: @@ -601,63 +737,119 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41 + sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022" url: "https://pub.dev" source: hosted - version: "2.0.18" + version: "2.1.1" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521 + sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.1.4" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310" + sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.2" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707" + sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc" + sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8" + sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5" url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.0" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436" + sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" source_span: dependency: transitive description: @@ -706,70 +898,86 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + test: + dependency: transitive + description: + name: test + sha256: "3dac9aecf2c3991d09b9cdde4f98ded7b30804a88a0d7e4e7e1678e78d6b97f4" + url: "https://pub.dev" + source: hosted + version: "1.24.1" test_api: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "5138dbffb77b2289ecb12b81c11ba46036590b72a64a7a90d6ffb880f1a29e93" + url: "https://pub.dev" + source: hosted + version: "0.5.1" timezone: dependency: "direct main" description: name: timezone - sha256: "24c8fcdd49a805d95777a39064862133ff816ebfffe0ceff110fb5960e557964" + sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0" url: "https://pub.dev" source: hosted - version: "0.9.1" + version: "0.9.2" typed_data: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 url: "https://pub.dev" source: hosted - version: "6.1.10" + version: "6.1.11" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "845530e5e05db5500c1a4c1446785d60cbd8f9bd45e21e7dd643a3273bb4bbd1" + sha256: "7aac14be5f4731b923cc697ae2d42043945076cd0dbb8806baecc92c1dc88891" url: "https://pub.dev" source: hosted - version: "6.0.25" + version: "6.0.33" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92" + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" url: "https://pub.dev" source: hosted - version: "6.1.3" + version: "6.1.4" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc" + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.5" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "0ef2b4f97942a16523e51256b799e9aa1843da6c60c55eefbfa9dbc2dcb8331a" + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.5" url_launcher_platform_interface: dependency: transitive description: @@ -790,10 +998,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd + sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" uuid: dependency: transitive description: @@ -818,14 +1026,38 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.3" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: f3743ca475e0c9ef71df4ba15eb2d7684eecd5c8ba20a462462e4e8b561b2e11 + url: "https://pub.dev" + source: hosted + version: "11.6.0" watcher: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" + source: hosted + version: "1.2.0" webview_flutter: dependency: transitive description: @@ -838,10 +1070,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: d6cf18cd6c809c5a9294cd99707a21986aac4e08c87e1916ce2590315fb55d3a + sha256: "1acea8def62592123e2fbbca164ed8681a98a890bdcbb88f916d5b4a22687759" url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.0" webview_flutter_platform_interface: dependency: transitive description: @@ -854,42 +1086,42 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: c94d242d8cbe1012c06ba7ac790c46d6e6b68723b7d34f8c74ed19f68d166f49 + sha256: "4646bb68297803bdbb96d46853e8fcb560d6cb5e04153fa64581535767875dfe" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.4.3" win32: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "4.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.0" xml: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=2.19.0 <3.0.0" - flutter: ">=3.7.7" + dart: ">=3.0.0 <4.0.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6dc9baa8..829c346e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,11 +3,11 @@ description: Mobile app to get the closing and opening schedules of the Chaban D publish_to: 'none' -version: 1.6.1 +version: 1.8.1 environment: - sdk: '>=2.17.6 <3.0.0' - flutter: '3.7.7' + sdk: '>=2.19.0 <3.0.0' + flutter: '3.10.0' dependencies: animated_toggle_switch: ^0.6.2 @@ -27,8 +27,8 @@ dependencies: google_fonts: ^4.0.0 google_mobile_ads: ^3.0.0 http: ^0.13.0 - intl: ^0.17.0 - package_info_plus: ^3.0.3 + intl: ^0.18.0 + package_info_plus: ^4.0.0 shared_preferences: ^2.0.17 stream_transform: ^2.0.0 timezone: ^0.9.1 @@ -42,6 +42,8 @@ dev_dependencies: flutter_lints: ^2.0.0 flutter_launcher_icons: ^0.13.0 dart_code_metrics: ^5.7.3 + bloc_test: ^9.1.1 + mockito: ^5.4.0 flutter: generate: true diff --git a/test/services/storage_service_test.dart b/test/services/storage_service_test.dart new file mode 100644 index 00000000..8b5f22ea --- /dev/null +++ b/test/services/storage_service_test.dart @@ -0,0 +1,101 @@ +import 'package:chabo/models/enums/day.dart'; +import 'package:chabo/models/enums/theme_state_status.dart'; +import 'package:chabo/models/time_slot.dart'; +import 'package:chabo/service/storage_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + late final StorageService storageService; + + setUpAll(() async { + SharedPreferences.setMockInitialValues({}); + final sharedPreferences = await SharedPreferences.getInstance(); + storageService = StorageService(sharedPreferences: sharedPreferences); + }); + + group('StorageService TESTS', () { + test('Save & Read String', () async { + final saveResult = + await storageService.saveString('KEY_STRING', 'STRING'); + final readResult = storageService.readString('KEY_STRING'); + expect(saveResult, true); + expect(readResult, 'STRING'); + }); + + test('Save & Read Bool', () async { + final saveResult = await storageService.saveBool('KEY_BOOL', false); + final readResult = storageService.readBool('KEY_BOOL'); + expect(saveResult, true); + expect(readResult, false); + }); + + test('Save & Read Duration', () async { + final saveResult = await storageService.saveDuration( + 'KEY_DURATION', + const Duration( + hours: 1, + ), + ); + final readResult = storageService.readDuration('KEY_DURATION'); + expect(saveResult, true); + expect(readResult, const Duration(hours: 1)); + }); + + test('Save & Read TimeOfDay', () async { + final saveResult = await storageService.saveTimeOfDay( + 'KEY_TIMEOFDAY', + const TimeOfDay( + hour: 13, + minute: 20, + ), + ); + final readResult = storageService.readTimeOfDay('KEY_TIMEOFDAY'); + expect(saveResult, true); + expect(readResult, const TimeOfDay(hour: 13, minute: 20)); + }); + + test('Save & Read Day', () async { + final saveResult = await storageService.saveDay( + 'KEY_DAY', + Day.monday, + ); + final readResult = storageService.readDay('KEY_DAY'); + expect(saveResult, true); + expect(readResult, Day.monday); + }); + + test('Save & Read Theme', () async { + final saveResult = await storageService.saveTheme( + 'KEY_THEME', + ThemeStateStatus.light, + ); + final readResult = storageService.readTheme('KEY_THEME'); + expect(saveResult, true); + expect(readResult, ThemeStateStatus.light); + }); + + test('Save & Read TimeSlots', () async { + final timeSlots = [ + const TimeSlot( + name: '', + from: TimeOfDay(hour: 8, minute: 0), + to: TimeOfDay(hour: 10, minute: 0), + ), + const TimeSlot( + name: '', + from: TimeOfDay(hour: 18, minute: 0), + to: TimeOfDay(hour: 20, minute: 0), + ), + ]; + final saveResult = await storageService.saveTimeSlots( + 'KEY_TIMESLOTS', + timeSlots, + ); + final readResult = storageService.readTimeSlots('KEY_TIMESLOTS'); + expect(saveResult, true); + expect(readResult, timeSlots); + }); + }); +} diff --git a/test/units/boat_forecast_test.dart b/test/units/boat_forecast_test.dart new file mode 100644 index 00000000..ee452a45 --- /dev/null +++ b/test/units/boat_forecast_test.dart @@ -0,0 +1,228 @@ +import 'package:chabo/models/boat.dart'; +import 'package:chabo/models/boat_forecast.dart'; +import 'package:chabo/models/enums/forecast_closing_type.dart'; +import 'package:chabo/models/time_slot.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final forecast = BoatForecast( + totalClosing: true, + circulationClosingDate: DateTime(2023, 5, 14, 15, 0), + circulationReOpeningDate: DateTime(2023, 5, 14, 16, 0), + boats: [Boat(name: 'TEST_BOAT', isLeaving: false)], + closingType: ForecastClosingType.complete, + ); + + final forecast2 = BoatForecast( + totalClosing: true, + circulationClosingDate: DateTime(2023, 5, 14, 23, 0), + circulationReOpeningDate: DateTime(2023, 5, 14, 05, 0), + boats: [Boat(name: 'TEST_BOAT', isLeaving: false)], + closingType: ForecastClosingType.complete, + ); + + final forecast3 = BoatForecast( + totalClosing: true, + circulationClosingDate: DateTime(2023, 5, 14, 23, 0), + circulationReOpeningDate: DateTime(2023, 5, 15, 05, 0), + boats: [Boat(name: 'TEST_BOAT', isLeaving: false)], + closingType: ForecastClosingType.complete, + ); + + group('BoatForecast TESTS', () { + test('Is during 2 days', () { + expect(forecast2.isDuringTwoDays, true); + }); + + test('Is NOT during 2 days', () { + expect(forecast.isDuringTwoDays, false); + }); + + test('Get the correct closing duration', () { + expect(forecast.closedDuration, const Duration(hours: 1)); + }); + + test('Is overlaping with', () { + final isOverlaping = + forecast.isOverlappingWith(DateTime(2023, 5, 14, 15, 30)); + expect(isOverlaping, true); + }); + + test('Is NOT overlaping with', () { + final isOverlaping = + forecast.isOverlappingWith(DateTime(2023, 5, 14, 17, 30)); + expect(isOverlaping, false); + }); + + test('(1) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 14, minute: 00), + to: TimeOfDay( + hour: 15, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(2) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 15, minute: 30), + to: TimeOfDay( + hour: 15, + minute: 45, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(3) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 12, minute: 30), + to: TimeOfDay( + hour: 18, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(4) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 15, minute: 30), + to: TimeOfDay( + hour: 18, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(5) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 15, minute: 30), + to: TimeOfDay( + hour: 18, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(6) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 23, minute: 00), + to: TimeOfDay( + hour: 23, + minute: 30, + ), + ); + + final isOverlaping = forecast3.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(7) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 00, minute: 00), + to: TimeOfDay( + hour: 1, + minute: 30, + ), + ); + + final isOverlaping = forecast3.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(1) Is NOT overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 19, minute: 00), + to: TimeOfDay( + hour: 20, + minute: 00, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, false); + }); + + test('(2) Is NOT overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 7, minute: 00), + to: TimeOfDay( + hour: 8, + minute: 30, + ), + ); + + final isOverlaping = forecast3.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, false); + }); + + test('Is NOT currently closed', () { + final isOverlaping = forecast.isCurrentlyClosed(); + expect(isOverlaping, false); + }); + + test('Has passed', () { + final isOverlaping = forecast.hasPassed(); + expect(isOverlaping, true); + }); + + test('Build from JSON - Boat arrival', () { + var jsonValue1 = { + 'datasetid': 'previsions_pont_chaban', + 'recordid': '82c316d304b1adef7eeef9edc9ab7b257f766086', + 'fields': { + 'fermeture_totale': 'oui', + 'bateau': 'MARITE', + 'date_passage': '2023-03-02', + 're_ouverture_a_la_circulation': '16:49', + 'fermeture_a_la_circulation': '15:49', + 'type_de_fermeture': 'Totale', + }, + 'record_timestamp': '2023-04-25T13:32:05.809+02:00', + }; + var jsonValue2 = { + 'datasetid': 'previsions_pont_chaban', + 'recordid': '82c316d304b1adef7eeef9edc9ab7b257f766086', + 'fields': { + 'fermeture_totale': 'oui', + 'bateau': 'MARITE', + 'date_passage': '2023-03-15', + 're_ouverture_a_la_circulation': '16:49', + 'fermeture_a_la_circulation': '15:49', + 'type_de_fermeture': 'Totale', + }, + 'record_timestamp': '2023-04-25T13:32:05.809+02:00', + }; + final forecast1 = BoatForecast.fromJSON(jsonValue1); + final forecast2 = BoatForecast.fromJSON(jsonValue2); + + expect(forecast1.boats[0].isLeaving, false); + expect(forecast2.boats[0].isLeaving, true); + }); + }); +} diff --git a/test/units/custom_extensions_tests.dart b/test/units/custom_extensions_tests.dart new file mode 100644 index 00000000..88bd21d3 --- /dev/null +++ b/test/units/custom_extensions_tests.dart @@ -0,0 +1,43 @@ +import 'package:chabo/extensions/date_time_extension.dart'; +import 'package:chabo/extensions/string_extension.dart'; +import 'package:chabo/models/enums/day.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('StringExtensions TEST', () { + test('Capitalize', () { + const string = 'test'; + expect(string.capitalize(), 'Test'); + }); + }); + + group('DateTimeExtensions TEST', () { + test('Previous 1', () { + const day = Day.monday; + final dateTime = DateTime(2023, 5, 14, 15, 0); + final previousMonday = dateTime.previous(day.weekPosition); + expect(previousMonday, DateTime(2023, 5, 8, 0, 0)); + }); + + test('Previous 2', () { + const day = Day.sunday; + final dateTime = DateTime(2023, 5, 14, 15, 0); + final previousMonday = dateTime.previous(day.weekPosition); + expect(previousMonday, DateTime(2023, 5, 7, 0, 0)); + }); + + test('Previous 3', () { + const day = Day.saturday; + final dateTime = DateTime(2023, 5, 14, 15, 0); + final previousMonday = dateTime.previous(day.weekPosition); + expect(previousMonday, DateTime(2023, 5, 13, 0, 0)); + }); + + test('Previous 4', () { + const day = Day.saturday; + final dateTime = DateTime(2023, 5, 11, 15, 0); + final previousMonday = dateTime.previous(day.weekPosition); + expect(previousMonday, DateTime(2023, 5, 6, 0, 0)); + }); + }); +} diff --git a/test/units/maintenance_forecast_test.dart b/test/units/maintenance_forecast_test.dart new file mode 100644 index 00000000..88f44ea5 --- /dev/null +++ b/test/units/maintenance_forecast_test.dart @@ -0,0 +1,190 @@ +import 'package:chabo/models/enums/forecast_closing_type.dart'; +import 'package:chabo/models/maintenance_forecast.dart'; +import 'package:chabo/models/time_slot.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final forecast = MaintenanceForecast( + totalClosing: true, + circulationClosingDate: DateTime(2023, 5, 14, 15, 0), + circulationReOpeningDate: DateTime(2023, 5, 14, 16, 0), + closingType: ForecastClosingType.complete, + ); + + final forecast2 = MaintenanceForecast( + totalClosing: true, + circulationClosingDate: DateTime(2023, 5, 14, 23, 0), + circulationReOpeningDate: DateTime(2023, 5, 14, 05, 0), + closingType: ForecastClosingType.complete, + ); + + final forecast3 = MaintenanceForecast( + totalClosing: true, + circulationClosingDate: DateTime(2023, 5, 14, 23, 0), + circulationReOpeningDate: DateTime(2023, 5, 15, 05, 0), + closingType: ForecastClosingType.complete, + ); + + group('MaintenanceForecast TESTS', () { + test('Is during 2 days', () { + expect(forecast2.isDuringTwoDays, true); + }); + + test('Is NOT during 2 days', () { + expect(forecast.isDuringTwoDays, false); + }); + + test('Get the correct closing duration', () { + expect(forecast.closedDuration, const Duration(hours: 1)); + }); + + test('Is overlaping with', () { + final isOverlaping = + forecast.isOverlappingWith(DateTime(2023, 5, 14, 15, 30)); + expect(isOverlaping, true); + }); + + test('Is NOT overlaping with', () { + final isOverlaping = + forecast.isOverlappingWith(DateTime(2023, 5, 14, 17, 30)); + expect(isOverlaping, false); + }); + + test('(1) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 14, minute: 00), + to: TimeOfDay( + hour: 15, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(2) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 15, minute: 30), + to: TimeOfDay( + hour: 15, + minute: 45, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(3) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 12, minute: 30), + to: TimeOfDay( + hour: 18, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(4) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 15, minute: 30), + to: TimeOfDay( + hour: 18, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(5) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 15, minute: 30), + to: TimeOfDay( + hour: 18, + minute: 30, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(6) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 23, minute: 00), + to: TimeOfDay( + hour: 23, + minute: 30, + ), + ); + + final isOverlaping = forecast3.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(7) Is overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 00, minute: 00), + to: TimeOfDay( + hour: 1, + minute: 30, + ), + ); + + final isOverlaping = forecast3.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, true); + }); + + test('(1) Is NOT overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 19, minute: 00), + to: TimeOfDay( + hour: 20, + minute: 00, + ), + ); + + final isOverlaping = forecast.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, false); + }); + + test('(2) Is NOT overlaping with [TimeSlots]', () { + const timeSlot1 = TimeSlot( + name: '', + from: TimeOfDay(hour: 7, minute: 00), + to: TimeOfDay( + hour: 8, + minute: 30, + ), + ); + + final isOverlaping = forecast3.isOverlappingWithTimeSlot(timeSlot1); + expect(isOverlaping, false); + }); + + test('Is NOT currently closed', () { + final isOverlaping = forecast.isCurrentlyClosed(); + expect(isOverlaping, false); + }); + + test('Has passed', () { + final isOverlaping = forecast.hasPassed(); + expect(isOverlaping, true); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index c3402014..00000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - expect(true, true); - }); -}