diff --git a/.github/composite/ci-build/action.yaml b/.github/composite/ci-build/action.yaml index 755f3028b..bd082dba0 100644 --- a/.github/composite/ci-build/action.yaml +++ b/.github/composite/ci-build/action.yaml @@ -15,9 +15,6 @@ inputs: asset-extension: required: true default: zip - github-api-token: - description: the Github API token - required: true app-name: required: true app-path: @@ -30,7 +27,7 @@ runs: steps: - name: Build the application shell: bash - run: flutter build -v ${{ inputs.build-target }} ${{ inputs.build-args }} --release --dart-define=GH_API_TOKEN=${{ inputs.github-api-token }} + run: flutter build -v ${{ inputs.build-target }} ${{ inputs.build-args }} --release env: MAPS_API_KEY: ${{ inputs.maps-api-key }} diff --git a/.github/workflows/dev-workflow.yaml b/.github/workflows/dev-workflow.yaml index da6368b61..c2330ac45 100644 --- a/.github/workflows/dev-workflow.yaml +++ b/.github/workflows/dev-workflow.yaml @@ -96,7 +96,6 @@ jobs: build-path: ${{ matrix.build_path }} asset-extension: ${{ matrix.asset_extension }} asset-content-type: ${{ matrix.asset_content_type }} - github-api-token: ${{ secrets.GH_API_TOKEN }} app-name: ${{ env.APP_NAME }} app-path: ${{ github.workspace }}/${{ env.APP_NAME }}_${{ matrix.target }}.${{ matrix.asset_extension }} maps-api-key: ${{ secrets.MAPS_API_KEY }} diff --git a/.github/workflows/master-workflow.yaml b/.github/workflows/master-workflow.yaml index 5c1c825f1..095e66845 100644 --- a/.github/workflows/master-workflow.yaml +++ b/.github/workflows/master-workflow.yaml @@ -116,7 +116,6 @@ jobs: build-path: ${{ matrix.build_path }} asset-extension: ${{ matrix.asset_extension }} asset-content-type: ${{ matrix.asset_content_type }} - github-api-token: ${{ secrets.GH_API_TOKEN }} app-name: ${{ env.APP_NAME }} app-path: ${{ github.workspace }}/${{ env.APP_NAME }}_${{ matrix.target }}.${{ matrix.asset_extension }} maps-api-key: ${{ secrets.MAPS_API_KEY }} diff --git a/.metadata b/.metadata index e27f5ccf8..e7e0854a8 100644 --- a/.metadata +++ b/.metadata @@ -1,11 +1,11 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled. +# This file should be version controlled and should not be manually edited. version: - revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - channel: stable + revision: "603104015dd692ea3403755b55d07813d5cf8965" + channel: "stable" project_type: app @@ -13,11 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 - platform: android - create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 - base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 + create_revision: 603104015dd692ea3403755b55d07813d5cf8965 + base_revision: 603104015dd692ea3403755b55d07813d5cf8965 - platform: ios create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 diff --git a/android/.gitignore b/android/.gitignore index bc2100d8f..55afd919c 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,3 +5,9 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle index c1604fe80..c7878c4e8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,54 +1,37 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +plugins { + id "com.android.application" + id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id "dev.flutter.flutter-gradle-plugin" + id 'com.google.gms.google-services' + id 'com.google.firebase.crashlytics' } - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('keystore.properties') if (keystorePropertiesFile.exists()) { keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" -apply plugin: 'com.google.gms.google-services' -apply plugin: 'com.google.firebase.crashlytics' - android { - compileSdkVersion flutter.targetSdkVersion - ndkVersion "26.3.11579264" + namespace = "ca.etsmtl.applets.etsmobile" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 + } defaultConfig { - applicationId "ca.etsmtl.applets.etsmobile" - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName - multiDexEnabled true + applicationId = "ca.etsmtl.applets.etsmobile" + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName manifestPlaceholders += [mapsApiKey: "$System.env.MAPS_API_KEY"] } @@ -80,7 +63,6 @@ android { ext.alwaysUpdateBuildId = false } } - namespace 'ca.etsmtl.applets.etsmobile' lint { checkDependencies true disable 'InvalidPackage' @@ -88,13 +70,5 @@ android { } flutter { - source '../..' + source = "../.." } - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.android.support:multidex:1.0.3' - implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' -} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index f880684a6..399f6981d 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,6 @@ - diff --git a/android/app/src/main/kotlin/ca/etsmtl/applets/etsmobile/MainActivity.kt b/android/app/src/main/kotlin/ca/etsmtl/applets/etsmobile/MainActivity.kt index 4346fc2e9..817611866 100644 --- a/android/app/src/main/kotlin/ca/etsmtl/applets/etsmobile/MainActivity.kt +++ b/android/app/src/main/kotlin/ca/etsmtl/applets/etsmobile/MainActivity.kt @@ -2,5 +2,4 @@ package ca.etsmtl.applets.etsmobile import io.flutter.embedding.android.FlutterActivity -class MainActivity : FlutterActivity() { -} \ No newline at end of file +class MainActivity: FlutterActivity() diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 000000000..06952be74 --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 1f83a33fd..cb1ef8805 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,18 +1,18 @@ - - - diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml index f880684a6..399f6981d 100644 --- a/android/app/src/profile/AndroidManifest.xml +++ b/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,6 @@ - diff --git a/android/build.gradle b/android/build.gradle index 2d132cc5f..663c1eb51 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.7.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.4.2' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1' @@ -21,19 +21,12 @@ allprojects { } } -rootProject.buildDir = '../build' -subprojects { - afterEvaluate { - android { - compileSdkVersion 34 - } - } -} +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } tasks.register("clean", Delete) { diff --git a/android/gradle.properties b/android/gradle.properties index 19f632fc6..7496dc98c 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ -org.gradle.jvmargs=-Xmx1536M -XX:+UseParallelGC +org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true extra-gen-snapshot-options=--obfuscate diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 3c472b99c..348c409ea 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index a8ea5755e..c73e3b40a 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,14 +1,25 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version '8.7.2' apply false + id "org.jetbrains.kotlin.android" version "1.8.22" apply false +} + +include ":app" diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 93646d98f..c8dd58122 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -19,18 +19,18 @@ PODS: - Firebase/RemoteConfig (11.4.0): - Firebase/CoreOnly - FirebaseRemoteConfig (~> 11.4.0) - - firebase_analytics (11.3.4): + - firebase_analytics (11.3.5): - Firebase/Analytics (= 11.4.0) - firebase_core - Flutter - - firebase_core (3.7.0): + - firebase_core (3.8.0): - Firebase/CoreOnly (= 11.4.0) - Flutter - - firebase_crashlytics (4.1.4): + - firebase_crashlytics (4.1.5): - Firebase/Crashlytics (= 11.4.0) - firebase_core - Flutter - - firebase_remote_config (5.1.4): + - firebase_remote_config (5.1.5): - Firebase/RemoteConfig (= 11.4.0) - firebase_core - Flutter @@ -60,7 +60,7 @@ PODS: - GoogleUtilities/Logger (~> 8.0) - FirebaseCoreExtension (11.4.1): - FirebaseCore (~> 11.0) - - FirebaseCoreInternal (11.4.2): + - FirebaseCoreInternal (11.5.0): - "GoogleUtilities/NSData+zlib (~> 8.0)" - FirebaseCrashlytics (11.4.0): - FirebaseCore (~> 11.4) @@ -84,7 +84,7 @@ PODS: - FirebaseSharedSwift (~> 11.0) - GoogleUtilities/Environment (~> 8.0) - "GoogleUtilities/NSData+zlib (~> 8.0)" - - FirebaseRemoteConfigInterop (11.4.0) + - FirebaseRemoteConfigInterop (11.5.0) - FirebaseSessions (11.4.0): - FirebaseCore (~> 11.4) - FirebaseCoreExtension (~> 11.4) @@ -94,10 +94,15 @@ PODS: - GoogleUtilities/UserDefaults (~> 8.0) - nanopb (~> 3.30910.0) - PromisesSwift (~> 2.1) - - FirebaseSharedSwift (11.4.0) + - FirebaseSharedSwift (11.5.0) - Flutter (1.0.0) - - flutter_custom_tabs_ios (2.0.0): + - flutter_inappwebview_ios (0.0.1): - Flutter + - flutter_inappwebview_ios/Core (= 0.0.1) + - OrderedSet (~> 6.0.3) + - flutter_inappwebview_ios/Core (0.0.1): + - Flutter + - OrderedSet (~> 6.0.3) - flutter_secure_storage (6.0.0): - Flutter - fluttertoast (0.0.2): @@ -171,6 +176,7 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - OrderedSet (6.0.3) - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -191,9 +197,6 @@ PODS: - Toast (4.1.1) - url_launcher_ios (0.0.1): - Flutter - - webview_flutter_wkwebview (0.0.1): - - Flutter - - FlutterMacOS DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) @@ -204,7 +207,7 @@ DEPENDENCIES: - firebase_crashlytics (from `.symlinks/plugins/firebase_crashlytics/ios`) - firebase_remote_config (from `.symlinks/plugins/firebase_remote_config/ios`) - Flutter (from `Flutter`) - - flutter_custom_tabs_ios (from `.symlinks/plugins/flutter_custom_tabs_ios/ios`) + - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`) @@ -216,7 +219,6 @@ DEPENDENCIES: - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - SwiftyXMLParser (from `https://github.com/yahoojapan/SwiftyXMLParser.git`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: @@ -238,6 +240,7 @@ SPEC REPOS: - GoogleMaps - GoogleUtilities - nanopb + - OrderedSet - PromisesObjC - PromisesSwift - Toast @@ -259,8 +262,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_remote_config/ios" Flutter: :path: Flutter - flutter_custom_tabs_ios: - :path: ".symlinks/plugins/flutter_custom_tabs_ios/ios" + flutter_inappwebview_ios: + :path: ".symlinks/plugins/flutter_inappwebview_ios/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" fluttertoast: @@ -283,8 +286,6 @@ EXTERNAL SOURCES: :git: https://github.com/yahoojapan/SwiftyXMLParser.git url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" - webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" CHECKOUT OPTIONS: SwiftyXMLParser: @@ -296,23 +297,23 @@ SPEC CHECKSUMS: device_calendar: 9cb33f88a02e19652ec7b8b122ca778f751b1f7b device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Firebase: cf1b19f21410b029b6786a54e9764a0cacad3c99 - firebase_analytics: 69a0f3e2ca2722c0758544eafcdcfa906b0f5227 - firebase_core: f8c5b220a8f9c436fdbd075ae321cef3d96ef181 - firebase_crashlytics: 4b31ac507cf01d25151cc34cbb0fec66d21c7138 - firebase_remote_config: 1b2da0c0a4f781a970ccf84597b8092e3ec69142 + firebase_analytics: fa7e5b20c2b58042e3301f5112a473d365bd490c + firebase_core: 9efc3ecf689cdbc90f13f4dc58108c83ea46b266 + firebase_crashlytics: 72a8b504422ba8bb435a7a0c0a9341320cbcbe29 + firebase_remote_config: 96a9b7e79624c5d9d16befdef60791966bb83919 FirebaseABTesting: aef1719704fade00b200827e7973f352efc4caee FirebaseAnalytics: 3feef9ae8733c567866342a1000691baaa7cad49 FirebaseCore: e0510f1523bc0eb21653cac00792e1e2bd6f1771 FirebaseCoreExtension: f1bc67a4702931a7caa097d8e4ac0a1b0d16720e - FirebaseCoreInternal: 35731192cab10797b88411be84940d2beb33a238 + FirebaseCoreInternal: f47dd28ae7782e6a4738aad3106071a8fe0af604 FirebaseCrashlytics: 41bbdd2b514a8523cede0c217aee6ef7ecf38401 FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414 FirebaseRemoteConfig: 7655681d02417bc9b287338edb9d721ff79e1a4a - FirebaseRemoteConfigInterop: e76f46ffa4d6a65e273d4dfebb6a79e588cec136 + FirebaseRemoteConfigInterop: 7a7aebb9342d53913a5c890efa88e289d9e5c1bc FirebaseSessions: 3f56f177d9e53a85021d16b31f9a111849d1dd8b - FirebaseSharedSwift: 505dae2d05969dbf6d43749a642bb1bf230f0252 + FirebaseSharedSwift: 302ac5967857ad7e7388b15382d705b8c8d892aa Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_custom_tabs_ios: a651b18786388923b62de8c0537607de87c2eccf + flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c Google-Maps-iOS-Utils: 66d6de12be1ce6d3742a54661e7a79cb317a9321 @@ -323,6 +324,7 @@ SPEC CHECKSUMS: GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 @@ -333,7 +335,6 @@ SPEC CHECKSUMS: SwiftyXMLParser: 027d9e6fb54a38d95dccec025bcea9693f699c47 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4 PODFILE CHECKSUM: 18f1615a0bcd417392c9107b3e8dc59c76a68dac diff --git a/lib/features/app/error/outage/widgets/outage_social_section.dart b/lib/features/app/error/outage/widgets/outage_social_section.dart index b0668b823..c23f3cff5 100644 --- a/lib/features/app/error/outage/widgets/outage_social_section.dart +++ b/lib/features/app/error/outage/widgets/outage_social_section.dart @@ -2,15 +2,16 @@ import 'package:flutter/material.dart'; // Package imports: -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // Project imports: import 'package:notredame/constants/urls.dart'; -import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/locator.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class OutageSocialSection extends StatelessWidget { - const OutageSocialSection({super.key}); + OutageSocialSection({super.key}); + final LaunchUrlService _launchUrlService = locator(); @override Widget build(BuildContext context) { @@ -27,28 +28,28 @@ class OutageSocialSection extends StatelessWidget { color: Colors.white, ), onPressed: () => - Utils.launchURL(Urls.clubWebsite, AppIntl.of(context)!)), - IconButton( + _launchUrlService.launchInBrowser(Urls.clubWebsite)), + IconButton( icon: const FaIcon( FontAwesomeIcons.github, color: Colors.white, ), onPressed: () => - Utils.launchURL(Urls.clubGithub, AppIntl.of(context)!)), + _launchUrlService.launchInBrowser(Urls.clubGithub)), IconButton( icon: const FaIcon( Icons.mail_outline, color: Colors.white, ), onPressed: () => - Utils.launchURL(Urls.clubEmail, AppIntl.of(context)!)), + _launchUrlService.writeEmail(Urls.clubEmail, "")), IconButton( icon: const FaIcon( FontAwesomeIcons.discord, color: Colors.white, ), onPressed: () => - Utils.launchURL(Urls.clubDiscord, AppIntl.of(context)!)), + _launchUrlService.launchInBrowser(Urls.clubDiscord)) ], ), ], diff --git a/lib/features/app/integration/launch_url_service.dart b/lib/features/app/integration/launch_url_service.dart index 14c634825..5269e968a 100644 --- a/lib/features/app/integration/launch_url_service.dart +++ b/lib/features/app/integration/launch_url_service.dart @@ -1,8 +1,7 @@ // Flutter imports: -import 'package:flutter/material.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; // Package imports: -import 'package:flutter_custom_tabs/flutter_custom_tabs.dart' as custom_tabs; import 'package:url_launcher/url_launcher.dart' as url_launch; // Project imports: @@ -12,58 +11,41 @@ import 'package:notredame/utils/locator.dart'; class LaunchUrlService { final SettingsManager settingsManager = locator(); + final browser = ChromeSafariBrowser(); - Future canLaunch(String url) async { - final uri = Uri.parse(url); - return url_launch.canLaunchUrl(uri); + Future writeEmail(String emailAddress, String subject) async { + final uri = Uri.parse('mailto:$emailAddress?subject=$subject'); + if (await url_launch.canLaunchUrl(uri)) { + url_launch.launchUrl(uri); + } else { + throw 'Could not send email to $emailAddress'; + } } - Future launch(String url) async { - final uri = Uri.parse(url); - return url_launch.launchUrl(uri); + Future call(String phoneNumber) async { + final uri = Uri.parse('tel:$phoneNumber'); + if (await url_launch.canLaunchUrl(uri)) { + url_launch.launchUrl(uri); + } else { + throw 'Could not call $phoneNumber'; + } } - Future launchInBrowser(String url, Brightness brightness) async { - await custom_tabs.launchUrl( - Uri.parse(url), - customTabsOptions: custom_tabs.CustomTabsOptions( - colorSchemes: custom_tabs.CustomTabsColorSchemes.defaults( - toolbarColor: brightness == Brightness.light - ? AppTheme.etsLightRed - : AppTheme.etsDarkRed), - shareState: custom_tabs.CustomTabsShareState.off, - urlBarHidingEnabled: true, - showTitle: true, - animations: custom_tabs.CustomTabsSystemAnimations.slideIn(), - browser: const custom_tabs.CustomTabsBrowserConfiguration( - fallbackCustomTabs: [ - // ref. https://play.google.com/store/apps/details?id=org.mozilla.firefox - 'org.mozilla.firefox', - // https://play.google.com/store/apps/details?id=com.brave.browser - 'com.brave.browser', - // https://play.google.com/store/apps/details?id=com.opera.browser - 'com.opera.browser', - 'com.opera.mini.native', - 'com.opera.gx', - // https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser - 'com.sec.android.app.sbrowser', - // ref. https://play.google.com/store/apps/details?id=com.microsoft.emmx - 'com.microsoft.emmx', - // https://play.google.com/store/apps/details?id=com.UCMobile.intl - 'com.UCMobile.intl', - ])), - safariVCOptions: custom_tabs.SafariViewControllerOptions( - preferredBarTintColor: brightness == Brightness.light - ? AppTheme.etsLightRed - : AppTheme.etsDarkRed, - preferredControlTintColor: brightness == Brightness.light - ? AppTheme.lightThemeBackground - : AppTheme.darkThemeBackground, - barCollapsingEnabled: true, - entersReaderIfAvailable: false, - dismissButtonStyle: - custom_tabs.SafariViewControllerDismissButtonStyle.close, - ), + void launchInBrowser(String url) { + browser.open( + url: WebUri(url), + settings: ChromeSafariBrowserSettings( + // Android + dismissButtonStyle: DismissButtonStyle.CLOSE, + enableUrlBarHiding: true, + toolbarBackgroundColor: AppTheme.accent, + navigationBarColor: AppTheme.primaryDark, + + // iOS + barCollapsingEnabled: true, + preferredControlTintColor: AppTheme.lightThemeBackground, + preferredBarTintColor: AppTheme.accent, + ) ); } } diff --git a/lib/features/app/navigation/router.dart b/lib/features/app/navigation/router.dart index 185848893..b7a8ddcdb 100644 --- a/lib/features/app/navigation/router.dart +++ b/lib/features/app/navigation/router.dart @@ -7,14 +7,12 @@ import 'package:notredame/features/app/error/outage/outage_view.dart'; import 'package:notredame/features/app/navigation/router_paths.dart'; import 'package:notredame/features/app/signets-api/models/course.dart'; import 'package:notredame/features/app/startup/startup_view.dart'; -import 'package:notredame/features/app/widgets/link_web_view.dart'; import 'package:notredame/features/dashboard/dashboard_view.dart'; import 'package:notredame/features/ets/ets_view.dart'; import 'package:notredame/features/ets/events/api-client/models/news.dart'; import 'package:notredame/features/ets/events/author/author_view.dart'; import 'package:notredame/features/ets/events/news/news-details/news_details_view.dart'; import 'package:notredame/features/ets/events/news/news_view.dart'; -import 'package:notredame/features/ets/quick-link/models/quick_link.dart'; import 'package:notredame/features/ets/quick-link/quick_links_view.dart'; import 'package:notredame/features/ets/quick-link/widgets/security-info/security_view.dart'; import 'package:notredame/features/more/about/about_view.dart'; @@ -106,10 +104,6 @@ Route generateRoute(RouteSettings routeSettings) { pageBuilder: (_, __, ___) => AuthorView( authorId: routeSettings.arguments! as String, )); - case RouterPaths.webView: - return MaterialPageRoute( - settings: RouteSettings(name: routeSettings.name), - builder: (_) => LinkWebView(routeSettings.arguments! as QuickLink)); case RouterPaths.security: return MaterialPageRoute( settings: RouteSettings(name: routeSettings.name), diff --git a/lib/features/app/navigation/router_paths.dart b/lib/features/app/navigation/router_paths.dart index fc03f0b9c..db01843fa 100644 --- a/lib/features/app/navigation/router_paths.dart +++ b/lib/features/app/navigation/router_paths.dart @@ -9,7 +9,6 @@ class RouterPaths { static const String student = "/student"; static const String gradeDetails = "/student/grade/details"; static const String ets = "/ets"; - static const String webView = "/ets/web-view"; static const String security = "/ets/security"; static const String usefulLinks = "/ets/useful-links"; static const String news = "/ets/news"; diff --git a/lib/features/app/widgets/base_scaffold.dart b/lib/features/app/widgets/base_scaffold.dart index a4a254871..d16fb3115 100644 --- a/lib/features/app/widgets/base_scaffold.dart +++ b/lib/features/app/widgets/base_scaffold.dart @@ -11,8 +11,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // Project imports: import 'package:notredame/features/app/integration/networking_service.dart'; -import 'package:notredame/features/app/widgets/bottom_bar.dart'; -import 'package:notredame/features/app/widgets/navigation_rail.dart'; +import 'package:notredame/features/app/widgets/navigation/bottom_bar.dart'; +import 'package:notredame/features/app/widgets/navigation/navigation_rail.dart'; import 'package:notredame/utils/app_theme.dart'; import 'package:notredame/utils/loading.dart'; import 'package:notredame/utils/locator.dart'; diff --git a/lib/features/app/widgets/bottom_bar.dart b/lib/features/app/widgets/bottom_bar.dart deleted file mode 100644 index 995bd0b8b..000000000 --- a/lib/features/app/widgets/bottom_bar.dart +++ /dev/null @@ -1,112 +0,0 @@ -// Flutter imports: -import 'package:flutter/material.dart'; - -// Package imports: -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -// Project imports: -import 'package:notredame/features/app/analytics/analytics_service.dart'; -import 'package:notredame/features/app/navigation/navigation_service.dart'; -import 'package:notredame/features/app/navigation/router_paths.dart'; -import 'package:notredame/utils/locator.dart'; - -/// Bottom navigation bar for the application. -class BottomBar extends StatefulWidget { - static const int dashboardView = 0; - static const int scheduleView = 1; - static const int studentView = 2; - static const int etsView = 3; - static const int moreView = 4; - - const BottomBar({super.key}); - - @override - State createState() => _BottomBarState(); -} - -class _BottomBarState extends State { - final NavigationService _navigationService = locator(); - final AnalyticsService _analyticsService = locator(); - - int _currentView = BottomBar.dashboardView; - - @override - Widget build(BuildContext context) { - _currentView = _defineIndex(ModalRoute.of(context)!.settings.name!); - return BottomNavigationBar( - type: BottomNavigationBarType.fixed, - elevation: 0, - onTap: (value) => _onTap(value), - items: _buildItems(context), - currentIndex: _currentView, - ); - } - - int _defineIndex(String routeName) { - switch (routeName) { - case RouterPaths.dashboard: - _currentView = BottomBar.dashboardView; - case RouterPaths.schedule: - _currentView = BottomBar.scheduleView; - case RouterPaths.student: - _currentView = BottomBar.studentView; - case RouterPaths.ets: - case RouterPaths.security: - case RouterPaths.news: - case RouterPaths.usefulLinks: - _currentView = BottomBar.etsView; - case RouterPaths.more: - case RouterPaths.settings: - case RouterPaths.about: - _currentView = BottomBar.moreView; - } - - return _currentView; - } - - void _onTap(int index) { - if (_currentView == index) { - return; - } - - switch (index) { - case BottomBar.dashboardView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.dashboard); - _analyticsService.logEvent("BottomBar", "DashboardView clicked"); - case BottomBar.scheduleView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.schedule); - _analyticsService.logEvent("BottomBar", "ScheduleView clicked"); - case BottomBar.studentView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.student); - _analyticsService.logEvent("BottomBar", "StudentView clicked"); - case BottomBar.etsView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.ets); - _analyticsService.logEvent("BottomBar", "EtsView clicked"); - case BottomBar.moreView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.more); - _analyticsService.logEvent("BottomBar", "MoreView clicked"); - } - _currentView = index; - } - - List _buildItems(BuildContext context) { - return [ - BottomNavigationBarItem( - icon: const Icon(Icons.dashboard_outlined), - label: AppIntl.of(context)!.title_dashboard), - BottomNavigationBarItem( - icon: const Icon(Icons.schedule_outlined), - label: AppIntl.of(context)!.title_schedule), - BottomNavigationBarItem( - icon: const Icon(Icons.school_outlined), - label: AppIntl.of(context)!.title_student), - BottomNavigationBarItem( - icon: const Icon(Icons.account_balance_outlined), - label: AppIntl.of(context)!.title_ets), - BottomNavigationBarItem( - icon: const Icon(Icons.menu_outlined), - label: AppIntl.of(context)!.title_more), - ]; - } - -} diff --git a/lib/features/app/widgets/link_web_view.dart b/lib/features/app/widgets/link_web_view.dart deleted file mode 100644 index c865b0497..000000000 --- a/lib/features/app/widgets/link_web_view.dart +++ /dev/null @@ -1,46 +0,0 @@ -// Flutter imports: -import 'package:flutter/material.dart'; - -// Package imports: -import 'package:webview_flutter/webview_flutter.dart'; - -// Project imports: -import 'package:notredame/features/app/widgets/base_scaffold.dart'; -import 'package:notredame/features/ets/quick-link/models/quick_link.dart'; - -class LinkWebView extends StatefulWidget { - final QuickLink _links; - - const LinkWebView(this._links, {super.key}); - - @override - State createState() => _LinkWebViewState(); -} - -class _LinkWebViewState extends State { - bool isLoading = true; - - @override - Widget build(BuildContext context) { - return BaseScaffold( - isLoading: isLoading, - showBottomBar: false, - appBar: AppBar( - title: Text(widget._links.name), - ), - body: Stack( - children: [ - WebViewWidget( - controller: WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setNavigationDelegate( - NavigationDelegate(onPageFinished: (String url) { - setState(() { - isLoading = false; - }); - }))), - ], - ), - ); - } -} diff --git a/lib/features/app/widgets/navigation/base_navigation_bar.dart b/lib/features/app/widgets/navigation/base_navigation_bar.dart new file mode 100644 index 000000000..d44074c0f --- /dev/null +++ b/lib/features/app/widgets/navigation/base_navigation_bar.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; + +import '../../../../utils/locator.dart'; +import '../../analytics/analytics_service.dart'; +import '../../navigation/navigation_service.dart'; +import '../../navigation/router_paths.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +enum NavigationView { + dashboard, + schedule, + student, + ets, + more, +} + +abstract class BaseNavigationBar extends StatefulWidget { + const BaseNavigationBar({super.key}); + + @override + BaseNavigationBarState createState(); + + Widget buildNavigationBar( + BuildContext context, NavigationView currentView, Function(NavigationView) onTap); + + List buildRailItems(BuildContext context) => + _buildItems(context, (icon, selectedIcon, label) => + NavigationRailDestination( + icon: Icon(icon), + selectedIcon: Icon(selectedIcon), + label: Text(label), + ), + ); + + List buildBottomBarItems(BuildContext context) => + _buildItems(context, (icon, selectedIcon, label) => + BottomNavigationBarItem( + icon: Icon(icon), + activeIcon: Icon(selectedIcon), + label: label, + ), + ); + + List _buildItems( + BuildContext context, + T Function(IconData, IconData?, String) builder, + ) { + final intl = AppIntl.of(context)!; + + return [ + builder(Icons.dashboard_outlined, Icons.dashboard, intl.title_dashboard), + builder(Icons.schedule_outlined, Icons.access_time_filled, intl.title_schedule), + builder(Icons.school_outlined, Icons.school, intl.title_student), + builder(Icons.account_balance_outlined, Icons.account_balance, intl.title_ets), + builder(Icons.menu_outlined, Icons.menu, intl.title_more), + ]; + } +} + +abstract class BaseNavigationBarState + extends State { + final NavigationService _navigationService = locator(); + final AnalyticsService _analyticsService = locator(); + + NavigationView _currentView = NavigationView.dashboard; + + @override + Widget build(BuildContext context) { + _currentView = _defineView(ModalRoute.of(context)!.settings.name!); + return widget.buildNavigationBar(context, _currentView, _onTap); + } + + NavigationView _defineView(String routeName) { + switch (routeName) { + case RouterPaths.dashboard: + return NavigationView.dashboard; + case RouterPaths.schedule: + return NavigationView.schedule; + case RouterPaths.student: + return NavigationView.student; + case RouterPaths.ets: + case RouterPaths.security: + return NavigationView.ets; + case RouterPaths.more: + case RouterPaths.settings: + case RouterPaths.about: + return NavigationView.more; + default: + return _currentView; + } + } + + void _onTap(NavigationView view) { + if (_currentView == view) return; + + final routeNames = { + NavigationView.dashboard: RouterPaths.dashboard, + NavigationView.schedule: RouterPaths.schedule, + NavigationView.student: RouterPaths.student, + NavigationView.ets: RouterPaths.ets, + NavigationView.more: RouterPaths.more + }; + final events = { + NavigationView.dashboard: "DashboardView clicked", + NavigationView.schedule: "ScheduleView clicked", + NavigationView.student: "StudentView clicked", + NavigationView.ets: "EtsView clicked", + NavigationView.more: "MoreView clicked" + }; + + _navigationService.pushNamedAndRemoveDuplicates(routeNames[view]!); + _analyticsService.logEvent("NavigationBar", events[view]!); + setState(() => _currentView = view); + } +} diff --git a/lib/features/app/widgets/navigation/bottom_bar.dart b/lib/features/app/widgets/navigation/bottom_bar.dart new file mode 100644 index 000000000..67c742291 --- /dev/null +++ b/lib/features/app/widgets/navigation/bottom_bar.dart @@ -0,0 +1,25 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:notredame/features/app/widgets/navigation/base_navigation_bar.dart'; + +class BottomBar extends BaseNavigationBar { + const BottomBar({super.key}); + + @override + Widget buildNavigationBar(BuildContext context, NavigationView currentView, Function(NavigationView) onTap) { + return BottomNavigationBar( + type: BottomNavigationBarType.fixed, + elevation: 0, + onTap: (value) => onTap(NavigationView.values[value]), + items: buildBottomBarItems(context), + currentIndex: currentView.index, + ); + } + + @override + BaseNavigationBarState createState() => _BottomBarState(); +} + +class _BottomBarState extends BaseNavigationBarState {} diff --git a/lib/features/app/widgets/navigation/navigation_rail.dart b/lib/features/app/widgets/navigation/navigation_rail.dart new file mode 100644 index 000000000..41183963f --- /dev/null +++ b/lib/features/app/widgets/navigation/navigation_rail.dart @@ -0,0 +1,26 @@ +// Flutter imports: +import 'package:flutter/material.dart'; + +// Package imports: +import 'package:notredame/features/app/widgets/navigation/base_navigation_bar.dart'; + +class NavRail extends BaseNavigationBar { + const NavRail({super.key}); + + @override + Widget buildNavigationBar(BuildContext context, NavigationView currentView, Function(NavigationView) onTap) { + return NavigationRail( + destinations: buildRailItems(context), + selectedIndex: currentView.index, + onDestinationSelected: (value) => onTap(NavigationView.values[value]), + labelType: MediaQuery.of(context).size.height > 350 + ? NavigationRailLabelType.all + : NavigationRailLabelType.selected, + ); + } + + @override + BaseNavigationBarState createState() => _NavigationRailState(); +} + +class _NavigationRailState extends BaseNavigationBarState {} diff --git a/lib/features/app/widgets/navigation_rail.dart b/lib/features/app/widgets/navigation_rail.dart deleted file mode 100644 index 5f628ef56..000000000 --- a/lib/features/app/widgets/navigation_rail.dart +++ /dev/null @@ -1,110 +0,0 @@ -// Flutter imports: -import 'package:flutter/material.dart'; - -// Package imports: -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -// Project imports: -import 'package:notredame/features/app/analytics/analytics_service.dart'; -import 'package:notredame/features/app/navigation/navigation_service.dart'; -import 'package:notredame/features/app/navigation/router_paths.dart'; -import 'package:notredame/utils/locator.dart'; - -/// Bottom navigation bar for the application. -class NavRail extends StatefulWidget { - static const int dashboardView = 0; - static const int scheduleView = 1; - static const int studentView = 2; - static const int etsView = 3; - static const int moreView = 4; - - const NavRail({super.key}); - - @override - State createState() => _NavRailState(); -} - -class _NavRailState extends State { - final NavigationService _navigationService = locator(); - final AnalyticsService _analyticsService = locator(); - - int _currentView = NavRail.dashboardView; - - @override - Widget build(BuildContext context) { - _currentView = _defineIndex(ModalRoute.of(context)!.settings.name!); - return NavigationRail( - destinations: _buildItems(context), - selectedIndex: _currentView, - onDestinationSelected: (value) => _onTap(value), - labelType: MediaQuery.of(context).size.height > 350 - ? NavigationRailLabelType.all - : NavigationRailLabelType.selected, - ); - } - - int _defineIndex(String routeName) { - switch (routeName) { - case RouterPaths.dashboard: - _currentView = NavRail.dashboardView; - case RouterPaths.schedule: - _currentView = NavRail.scheduleView; - case RouterPaths.student: - _currentView = NavRail.studentView; - case RouterPaths.ets: - case RouterPaths.security: - _currentView = NavRail.etsView; - case RouterPaths.more: - case RouterPaths.settings: - case RouterPaths.about: - _currentView = NavRail.moreView; - } - - return _currentView; - } - - void _onTap(int index) { - if (_currentView == index) { - return; - } - - switch (index) { - case NavRail.dashboardView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.dashboard); - _analyticsService.logEvent("BottomBar", "DashboardView clicked"); - case NavRail.scheduleView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.schedule); - _analyticsService.logEvent("BottomBar", "ScheduleView clicked"); - case NavRail.studentView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.student); - _analyticsService.logEvent("BottomBar", "StudentView clicked"); - case NavRail.etsView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.ets); - _analyticsService.logEvent("BottomBar", "EtsView clicked"); - case NavRail.moreView: - _navigationService.pushNamedAndRemoveDuplicates(RouterPaths.more); - _analyticsService.logEvent("BottomBar", "MoreView clicked"); - } - _currentView = index; - } - - List _buildItems(BuildContext context) { - return [ - NavigationRailDestination( - icon: const Icon(Icons.dashboard_outlined), - label: Text(AppIntl.of(context)!.title_dashboard)), - NavigationRailDestination( - icon: const Icon(Icons.schedule_outlined), - label: Text(AppIntl.of(context)!.title_schedule)), - NavigationRailDestination( - icon: const Icon(Icons.school_outlined), - label: Text(AppIntl.of(context)!.title_student)), - NavigationRailDestination( - icon: const Icon(Icons.account_balance_outlined), - label: Text(AppIntl.of(context)!.title_ets)), - NavigationRailDestination( - icon: const Icon(Icons.menu_outlined), - label: Text(AppIntl.of(context)!.title_more)), - ]; - } -} diff --git a/lib/features/dashboard/dashboard_view.dart b/lib/features/dashboard/dashboard_view.dart index 28417fdd2..5680a46e3 100644 --- a/lib/features/dashboard/dashboard_view.dart +++ b/lib/features/dashboard/dashboard_view.dart @@ -27,7 +27,7 @@ import 'package:notredame/features/student/grades/widgets/grade_button.dart'; import 'package:notredame/utils/app_theme.dart'; import 'package:notredame/utils/loading.dart'; import 'package:notredame/utils/locator.dart'; -import 'package:notredame/utils/utils.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class DashboardView extends StatefulWidget { const DashboardView({super.key}); @@ -39,6 +39,7 @@ class DashboardView extends StatefulWidget { class _DashboardViewState extends State with TickerProviderStateMixin { Text? progressBarText; + final LaunchUrlService _launchUrlService = locator(); final NavigationService _navigationService = locator(); final AnalyticsService _analyticsService = locator(); static const String tag = "DashboardView"; @@ -146,8 +147,7 @@ class _DashboardViewState extends State IconButton( onPressed: () { _analyticsService.logEvent(tag, "Facebook clicked"); - Utils.launchURL( - Urls.clubFacebook, AppIntl.of(context)!); + _launchUrlService.launchInBrowser(Urls.clubFacebook); }, icon: const FaIcon( FontAwesomeIcons.facebook, @@ -157,8 +157,7 @@ class _DashboardViewState extends State IconButton( onPressed: () { _analyticsService.logEvent(tag, "Instagram clicked"); - Utils.launchURL( - Urls.clubInstagram, AppIntl.of(context)!); + _launchUrlService.launchInBrowser(Urls.clubInstagram); }, icon: const FaIcon( FontAwesomeIcons.instagram, @@ -168,8 +167,8 @@ class _DashboardViewState extends State IconButton( onPressed: () { _analyticsService.logEvent(tag, "Github clicked"); - Utils.launchURL(Urls.clubGithub, AppIntl.of(context)!); - }, + _launchUrlService.launchInBrowser(Urls.clubGithub); + }, icon: const FaIcon( FontAwesomeIcons.github, color: Colors.white, @@ -178,7 +177,7 @@ class _DashboardViewState extends State IconButton( onPressed: () { _analyticsService.logEvent(tag, "Email clicked"); - Utils.launchURL(Urls.clubEmail, AppIntl.of(context)!); + _launchUrlService.writeEmail(Urls.clubEmail, ""); }, icon: const FaIcon( FontAwesomeIcons.envelope, @@ -188,8 +187,8 @@ class _DashboardViewState extends State IconButton( onPressed: () { _analyticsService.logEvent(tag, "Discord clicked"); - Utils.launchURL(Urls.clubDiscord, AppIntl.of(context)!); - }, + _launchUrlService.launchInBrowser(Urls.clubDiscord); + }, icon: const FaIcon( FontAwesomeIcons.discord, color: Colors.white, diff --git a/lib/features/dashboard/dashboard_viewmodel.dart b/lib/features/dashboard/dashboard_viewmodel.dart index 8bf0dd2b3..c994f54f7 100644 --- a/lib/features/dashboard/dashboard_viewmodel.dart +++ b/lib/features/dashboard/dashboard_viewmodel.dart @@ -1,9 +1,6 @@ // Dart imports: import 'dart:collection'; -// Flutter imports: -import 'package:flutter/material.dart'; - // Package imports: import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -15,8 +12,6 @@ import 'package:notredame/constants/preferences_flags.dart'; import 'package:notredame/features/app/analytics/analytics_service.dart'; import 'package:notredame/features/app/analytics/remote_config_service.dart'; import 'package:notredame/features/app/integration/launch_url_service.dart'; -import 'package:notredame/features/app/navigation/navigation_service.dart'; -import 'package:notredame/features/app/navigation/router_paths.dart'; import 'package:notredame/features/app/repository/course_repository.dart'; import 'package:notredame/features/app/signets-api/models/course.dart'; import 'package:notredame/features/app/signets-api/models/course_activity.dart'; @@ -141,13 +136,7 @@ class DashboardViewModel extends FutureViewModel> { static Future launchBroadcastUrl(String url) async { final LaunchUrlService launchUrlService = locator(); - final NavigationService navigationService = locator(); - try { - await launchUrlService.launchInBrowser(url, Brightness.light); - } catch (error) { - // An exception is thrown if browser app is not installed on Android device. - await navigationService.pushNamed(RouterPaths.webView, arguments: url); - } + launchUrlService.launchInBrowser(url); } void changeProgressBarText() { diff --git a/lib/features/ets/events/social/social_links_card.dart b/lib/features/ets/events/social/social_links_card.dart index 502a1b5f0..4f60eb56d 100644 --- a/lib/features/ets/events/social/social_links_card.dart +++ b/lib/features/ets/events/social/social_links_card.dart @@ -152,7 +152,7 @@ class _SocialLinksState extends State { Widget _buildSocialButton(SocialLink link, WebLinkCardViewModel model) { return IconButton( icon: link.image, - onPressed: () => model.onLinkClicked(link, Theme.of(context).brightness), + onPressed: () => model.onLinkClicked(link), ); } } diff --git a/lib/features/ets/quick-link/widgets/security-info/emergency_view.dart b/lib/features/ets/quick-link/widgets/security-info/emergency_view.dart index c66cb75e1..6666b4c2d 100644 --- a/lib/features/ets/quick-link/widgets/security-info/emergency_view.dart +++ b/lib/features/ets/quick-link/widgets/security-info/emergency_view.dart @@ -9,7 +9,8 @@ import 'package:flutter_markdown/flutter_markdown.dart'; // Project imports: import 'package:notredame/features/app/widgets/base_scaffold.dart'; import 'package:notredame/utils/app_theme.dart'; -import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/locator.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class EmergencyView extends StatefulWidget { final String title; @@ -22,6 +23,8 @@ class EmergencyView extends StatefulWidget { } class _EmergencyViewState extends State { + final LaunchUrlService _launchUrlService = locator(); + @override Widget build(BuildContext context) => BaseScaffold( appBar: AppBar(title: Text(widget.title)), @@ -31,9 +34,7 @@ class _EmergencyViewState extends State { fab: FloatingActionButton.extended( onPressed: () { try { - Utils.launchURL( - 'tel:${AppIntl.of(context)!.security_emergency_number}', - AppIntl.of(context)!); + _launchUrlService.call(AppIntl.of(context)!.security_emergency_number); } catch (e) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(e.toString()))); diff --git a/lib/features/ets/quick-link/widgets/security-info/security_view.dart b/lib/features/ets/quick-link/widgets/security-info/security_view.dart index fd86f7dcd..26f47ca58 100644 --- a/lib/features/ets/quick-link/widgets/security-info/security_view.dart +++ b/lib/features/ets/quick-link/widgets/security-info/security_view.dart @@ -13,7 +13,8 @@ import 'package:notredame/features/app/widgets/base_scaffold.dart'; import 'package:notredame/features/ets/quick-link/widgets/security-info/emergency_view.dart'; import 'package:notredame/features/ets/quick-link/widgets/security-info/security_viewmodel.dart'; import 'package:notredame/utils/app_theme.dart'; -import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/locator.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class SecurityView extends StatefulWidget { const SecurityView({super.key}); @@ -23,6 +24,7 @@ class SecurityView extends StatefulWidget { } class _SecurityViewState extends State { + final LaunchUrlService _launchUrlService = locator(); static const CameraPosition _etsLocation = CameraPosition( target: LatLng(45.49449875, -73.56246144109338), zoom: 17.0); @@ -84,9 +86,7 @@ class _SecurityViewState extends State { splashColor: Colors.red.withAlpha(50), onTap: () { try { - Utils.launchURL( - 'tel:${AppIntl.of(context)!.security_emergency_number}', - AppIntl.of(context)!); + _launchUrlService.call(AppIntl.of(context)!.security_emergency_number); } catch (e) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(e.toString()))); diff --git a/lib/features/ets/quick-link/widgets/web_link_card.dart b/lib/features/ets/quick-link/widgets/web_link_card.dart index 890afe02c..121e12af0 100644 --- a/lib/features/ets/quick-link/widgets/web_link_card.dart +++ b/lib/features/ets/quick-link/widgets/web_link_card.dart @@ -26,7 +26,7 @@ class WebLinkCard extends StatelessWidget { child: InkWell( borderRadius: const BorderRadius.all(Radius.circular(10)), onTap: () => - model.onLinkClicked(_links, Theme.of(context).brightness), + model.onLinkClicked(_links), splashColor: AppTheme.etsLightRed.withAlpha(50), child: Padding( padding: const EdgeInsets.all(8.0), diff --git a/lib/features/ets/quick-link/widgets/web_link_card_viewmodel.dart b/lib/features/ets/quick-link/widgets/web_link_card_viewmodel.dart index c7fa0a96a..97f089fde 100644 --- a/lib/features/ets/quick-link/widgets/web_link_card_viewmodel.dart +++ b/lib/features/ets/quick-link/widgets/web_link_card_viewmodel.dart @@ -1,7 +1,3 @@ -// Flutter imports: -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; - // Package imports: import 'package:stacked/stacked.dart'; @@ -22,22 +18,13 @@ class WebLinkCardViewModel extends BaseViewModel { final LaunchUrlService _launchUrlService = locator(); /// used to open a website or the security view - Future onLinkClicked(QuickLink link, Brightness brightness) async { + Future onLinkClicked(QuickLink link) async { _analyticsService.logEvent("QuickLink", "QuickLink clicked: ${link.name}"); if (link.link == 'security') { _navigationService.pushNamed(RouterPaths.security); } else { - try { - await _launchUrlService.launchInBrowser(link.link, brightness); - } catch (error) { - // An exception is thrown if browser app is not installed on Android device. - await launchWebView(link); - } + _launchUrlService.launchInBrowser(link.link); } } - - Future launchWebView(QuickLink link) async { - _navigationService.pushNamed(RouterPaths.webView, arguments: link); - } } diff --git a/lib/features/more/about/about_view.dart b/lib/features/more/about/about_view.dart index 28ab61352..b41834b43 100644 --- a/lib/features/more/about/about_view.dart +++ b/lib/features/more/about/about_view.dart @@ -8,7 +8,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; // Project imports: import 'package:notredame/constants/urls.dart'; -import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/locator.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class AboutView extends StatefulWidget { const AboutView({super.key}); @@ -18,6 +19,7 @@ class AboutView extends StatefulWidget { } class _AboutViewState extends State with TickerProviderStateMixin { + final LaunchUrlService _launchUrlService = locator(); late AnimationController _controller; bool _completed = false; bool _easterEggTrigger = false; @@ -128,43 +130,43 @@ class _AboutViewState extends State with TickerProviderStateMixin { FontAwesomeIcons.earthAmericas, color: Colors.white, ), - onPressed: () => Utils.launchURL( - Urls.clubWebsite, AppIntl.of(context)!)), + onPressed: () => + _launchUrlService.launchInBrowser(Urls.clubWebsite)), IconButton( icon: const FaIcon( FontAwesomeIcons.github, color: Colors.white, ), - onPressed: () => Utils.launchURL( - Urls.clubGithub, AppIntl.of(context)!)), + onPressed: () => + _launchUrlService.launchInBrowser(Urls.clubGithub)), IconButton( icon: const FaIcon( FontAwesomeIcons.facebook, color: Colors.white, ), - onPressed: () => Utils.launchURL( - Urls.clubFacebook, AppIntl.of(context)!)), + onPressed: () => + _launchUrlService.launchInBrowser(Urls.clubFacebook)), IconButton( icon: const FaIcon( FontAwesomeIcons.twitter, color: Colors.white, ), - onPressed: () => Utils.launchURL( - Urls.clubTwitter, AppIntl.of(context)!)), + onPressed: () => + _launchUrlService.launchInBrowser(Urls.clubTwitter)), IconButton( icon: const FaIcon( FontAwesomeIcons.youtube, color: Colors.white, ), - onPressed: () => Utils.launchURL( - Urls.clubYoutube, AppIntl.of(context)!)), + onPressed: () => + _launchUrlService.launchInBrowser(Urls.clubYoutube)), IconButton( icon: const FaIcon( FontAwesomeIcons.discord, color: Colors.white, ), - onPressed: () => Utils.launchURL( - Urls.clubDiscord, AppIntl.of(context)!)), + onPressed: () => + _launchUrlService.launchInBrowser(Urls.clubDiscord)), ], ), ), diff --git a/lib/features/more/contributors/contributors_view.dart b/lib/features/more/contributors/contributors_view.dart index 33c3ceec4..2409c9443 100644 --- a/lib/features/more/contributors/contributors_view.dart +++ b/lib/features/more/contributors/contributors_view.dart @@ -10,10 +10,12 @@ import 'package:stacked/stacked.dart'; // Project imports: import 'package:notredame/features/app/widgets/base_scaffold.dart'; import 'package:notredame/features/more/contributors/contributors_viewmodel.dart'; -import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/locator.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class ContributorsView extends StatelessWidget { - const ContributorsView({super.key}); + ContributorsView({super.key}); + final LaunchUrlService _launchUrlService = locator(); @override Widget build(BuildContext context) => @@ -54,8 +56,8 @@ class ContributorsView extends StatelessWidget { leading: CircleAvatar( backgroundColor: Colors.grey, backgroundImage: NetworkImage(contributors[index].avatarUrl ?? '')), - onTap: () => Utils.launchURL( - contributors[index].htmlUrl ?? '', AppIntl.of(context)!), + onTap: () => + _launchUrlService.launchInBrowser(contributors[index].htmlUrl ?? ''), ), ); } diff --git a/lib/features/more/faq/faq_view.dart b/lib/features/more/faq/faq_view.dart index c2f893eaa..c7c1e0b94 100644 --- a/lib/features/more/faq/faq_view.dart +++ b/lib/features/more/faq/faq_view.dart @@ -184,9 +184,9 @@ class _FaqViewState extends State { child: ElevatedButton( onPressed: () { if (type.name == ActionType.webview.name) { - openWebview(model, link); + model.launchWebsite(link); } else if (type.name == ActionType.email.name) { - openMail(model, context, link); + model.openMail(link, context); } }, style: ButtonStyle( @@ -253,13 +253,4 @@ class _FaqViewState extends State { ), ); } - - Future openWebview(FaqViewModel model, String link) async { - model.launchWebsite(link, Theme.of(context).brightness); - } - - Future openMail( - FaqViewModel model, BuildContext context, String addressEmail) async { - model.openMail(addressEmail, context); - } } diff --git a/lib/features/more/faq/faq_viewmodel.dart b/lib/features/more/faq/faq_viewmodel.dart index 424870280..e836e2ed0 100644 --- a/lib/features/more/faq/faq_viewmodel.dart +++ b/lib/features/more/faq/faq_viewmodel.dart @@ -19,27 +19,20 @@ class FaqViewModel extends BaseViewModel { Locale? get locale => _settingsManager.locale; - String mailtoStr(String email, String subject) { - return 'mailto:$email?subject=$subject'; - } - - Future launchWebsite(String link, Brightness brightness) async { - await _launchUrlService.launchInBrowser(link, brightness); + void launchWebsite(String link) { + _launchUrlService.launchInBrowser(link); } Future openMail(String addressEmail, BuildContext context) async { - var email = ""; + String subject = ""; + if (addressEmail == AppInfo.email) { - email = mailtoStr(addressEmail, AppIntl.of(context)!.email_subject); - } else { - email = mailtoStr(addressEmail, ""); + subject = AppIntl.of(context)!.email_subject; } - final urlLaunchable = await _launchUrlService.canLaunch(email); - - if (urlLaunchable) { - await _launchUrlService.launch(email); - } else { + try { + _launchUrlService.writeEmail(addressEmail, subject); + } catch (e) { locator().logError("login_view", "Cannot send email."); } } diff --git a/lib/features/more/feedback/feedback_view.dart b/lib/features/more/feedback/feedback_view.dart index 5fe59db1f..9bdda1c6a 100644 --- a/lib/features/more/feedback/feedback_view.dart +++ b/lib/features/more/feedback/feedback_view.dart @@ -11,7 +11,8 @@ import 'package:notredame/features/more/feedback/feedback_type.dart'; import 'package:notredame/features/more/feedback/feedback_viewmodel.dart'; import 'package:notredame/utils/app_theme.dart'; import 'package:notredame/utils/loading.dart'; -import 'package:notredame/utils/utils.dart'; +import 'package:notredame/utils/locator.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class FeedbackView extends StatefulWidget { const FeedbackView({super.key}); @@ -21,6 +22,8 @@ class FeedbackView extends StatefulWidget { } class _FeedbackViewState extends State { + final LaunchUrlService _launchUrlService = locator(); + @override Widget build(BuildContext context) => ViewModelBuilder.reactive( @@ -116,8 +119,7 @@ class _FeedbackViewState extends State { Theme.of(context).brightness == Brightness.light; return GestureDetector( onTap: () => { - Utils.launchURL(model.myIssues[index].htmlUrl, - AppIntl.of(context)!) + _launchUrlService.launchInBrowser(model.myIssues[index].htmlUrl) }, child: Container( margin: const EdgeInsets.only( diff --git a/lib/features/more/more_view.dart b/lib/features/more/more_view.dart index 5d61b73de..e561c2ee4 100644 --- a/lib/features/more/more_view.dart +++ b/lib/features/more/more_view.dart @@ -14,6 +14,7 @@ import 'package:notredame/features/more/more_viewmodel.dart'; import 'package:notredame/utils/app_theme.dart'; import 'package:notredame/utils/locator.dart'; import 'package:notredame/utils/utils.dart'; +import 'package:notredame/features/app/integration/launch_url_service.dart'; class MoreView extends StatefulWidget { const MoreView({super.key}); @@ -23,6 +24,7 @@ class MoreView extends StatefulWidget { } class _MoreViewState extends State { + final LaunchUrlService _launchUrlService = locator(); final AnalyticsService _analyticsService = locator(); static const String tag = "MoreView"; @@ -47,9 +49,8 @@ class _MoreViewState extends State { style: textStyle.copyWith(color: Colors.blue), text: AppIntl.of(context)!.flutter_website, recognizer: TapGestureRecognizer() - ..onTap = () => Utils.launchURL( - AppIntl.of(context)!.flutter_website, - AppIntl.of(context)!)), + ..onTap = () => + _launchUrlService.launchInBrowser(AppIntl.of(context)!.flutter_website)), TextSpan(style: textStyle, text: '.'), ], ), diff --git a/lib/features/more/more_viewmodel.dart b/lib/features/more/more_viewmodel.dart index e2d3a391c..0b4f9fe9e 100644 --- a/lib/features/more/more_viewmodel.dart +++ b/lib/features/more/more_viewmodel.dart @@ -1,6 +1,3 @@ -// Flutter imports: -import 'package:flutter/material.dart'; - // Package imports: import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:fluttertoast/fluttertoast.dart'; @@ -115,17 +112,8 @@ class MoreViewModel extends FutureViewModel { static Future launchPrivacyPolicy() async { final LaunchUrlService launchUrlService = locator(); - final RemoteConfigService remoteConfigService = - locator(); - final NavigationService navigationService = locator(); - try { - await launchUrlService.launchInBrowser( - remoteConfigService.privacyPolicyUrl, Brightness.light); - } catch (error) { - // An exception is thrown if browser app is not installed on Android device. - await navigationService.pushNamed(RouterPaths.webView, - arguments: remoteConfigService.privacyPolicyUrl); - } + final RemoteConfigService remoteConfigService = locator(); + launchUrlService.launchInBrowser(remoteConfigService.privacyPolicyUrl); } /// Get the privacy policy toggle diff --git a/lib/features/schedule/schedule_view.dart b/lib/features/schedule/schedule_view.dart index b8b42f3b1..673f2d6f3 100644 --- a/lib/features/schedule/schedule_view.dart +++ b/lib/features/schedule/schedule_view.dart @@ -142,7 +142,7 @@ class _ScheduleViewState extends State const SizedBox(height: 8.0), const Divider(indent: 8.0, endIndent: 8.0, thickness: 1), const SizedBox(height: 6.0), - _buildTitleForDate(model.daySelected, model), + _buildTitleForDate(model.daySelected, model), const SizedBox(height: 2.0), if (model.selectedDateEvents(model.daySelected).isEmpty && !model.isBusy) Padding( @@ -327,12 +327,12 @@ class _ScheduleViewState extends State Icons.chevron_left, size: 30, color: chevronColor, - ), + ), rightIcon: Icon( Icons.chevron_right, size: 30, color: chevronColor, - )), + )), weekDayStringBuilder: (p0) { return weekTitles[p0]; }, diff --git a/lib/features/welcome/widgets/forgot_password.dart b/lib/features/welcome/widgets/forgot_password.dart index 2b7199c97..50c349f86 100644 --- a/lib/features/welcome/widgets/forgot_password.dart +++ b/lib/features/welcome/widgets/forgot_password.dart @@ -44,9 +44,7 @@ class _ForgotPasswordState extends State{ .signetsPasswordResetUrl; if (signetsPasswordResetUrl != "") { _launchUrlService.launchInBrowser( - _remoteConfigService - .signetsPasswordResetUrl, - Theme.of(context).brightness); + _remoteConfigService.signetsPasswordResetUrl); } else { Fluttertoast.showToast( msg: AppIntl.of(context)! diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 4c8bded4d..75d3083b2 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -1,23 +1,7 @@ // Flutter imports: import 'package:flutter/material.dart'; -// Package imports: -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:url_launcher/url_launcher.dart'; - mixin Utils { - /// Used to open a url - static Future launchURL(String url, AppIntl intl) async { - final uri = Uri.parse(url); - if (await canLaunchUrl(uri)) { - await launchUrl(uri); - } else { - Fluttertoast.showToast(msg: intl.error); - throw 'Could not launch $url'; - } - } - static double getGradeInPercentage(double? grade, double? maxGrade) { if (grade == null || maxGrade == null || grade == 0.0 || maxGrade == 0.0) { return 0.0; diff --git a/pubspec.lock b/pubspec.lock index fd3b55a4f..4518f6122 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "5a0296da7ae717ffb7444dee8439ca25ac80e162a345b933aa57f0a4a48dca2c" + sha256: "71c01c1998c40b3af1944ad0a5f374b4e6fef7f3d2df487f3970dbeadaeb25a1" url: "https://pub.dev" source: hosted - version: "1.3.45" + version: "1.3.46" analyzer: dependency: transitive description: @@ -325,34 +325,34 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: "3215f9d347530ce604346dd3ddb71937c46fedd6e6943aea2c2d2829dc655af0" + sha256: "8be7c45091f01cc15130edf8201ed4f4b7b022a38424ed9aac6b9a6d7c45bb09" url: "https://pub.dev" source: hosted - version: "11.3.4" + version: "11.3.5" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: "35148db6e0cc5142bb90f1664d6d0081bd7554749585404d18b5b14452776b3e" + sha256: "111e288dd332ce13e1ec96b54f5dca0601fe6e75bc251f74fd6f70096f3fbf09" url: "https://pub.dev" source: hosted - version: "4.2.6" + version: "4.2.7" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "403a705438405499a16cdf66da03e7f39cf5faad3e44a6390699a84c34fce953" + sha256: "7c3c80b4e223565ddbda3eaacc42712ba6de53410f8ae18738c807aa8af6b910" url: "https://pub.dev" source: hosted - version: "0.5.10+3" + version: "0.5.10+4" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: e59141ff83e70a9ba571a1f8733c5598cf57e6e68037ab185581d7fc0a436738 + sha256: "2438a75ad803e818ad3bd5df49137ee619c46b6fc7101f4dbc23da07305ce553" url: "https://pub.dev" source: hosted - version: "3.7.0" + version: "3.8.0" firebase_core_platform_interface: dependency: transitive description: @@ -373,42 +373,42 @@ packages: dependency: "direct main" description: name: firebase_crashlytics - sha256: "859c0f9c40f44b555fc44227c3506b4236b86e6680cafb837e1eef40e661956e" + sha256: "4e80ef22428dfecf609df8049419c7446c6e1d797d7f307cad3c7ab70e72ddc5" url: "https://pub.dev" source: hosted - version: "4.1.4" + version: "4.1.5" firebase_crashlytics_platform_interface: dependency: transitive description: name: firebase_crashlytics_platform_interface - sha256: "4ae5bc279fd0a352d684340f9250b25571386fee0f7f3a750aae11c1cbbeaa40" + sha256: "1104f428ec5249fff62016985719bb232ca91c4bde0d1a033af9b7d8b7451d70" url: "https://pub.dev" source: hosted - version: "3.6.45" + version: "3.6.46" firebase_remote_config: dependency: "direct main" description: name: firebase_remote_config - sha256: "10ecace220616349263c8b6b80760f302d4bc68e6a9db2800c615ab06853de46" + sha256: "29619302396823622dd9d0ccb8ac2b48804c518c0c82aa85ef3d86b91fdf55ca" url: "https://pub.dev" source: hosted - version: "5.1.4" + version: "5.1.5" firebase_remote_config_platform_interface: dependency: transitive description: name: firebase_remote_config_platform_interface - sha256: "2595a3def5dfab965b8a2cfd272a5595dec464a7a593258cfa9e4144cbe9842c" + sha256: ae899364a77fdf706c2ec37f1336df1dd3cc6777f8cd6ce7cf0cae65830bf1cf url: "https://pub.dev" source: hosted - version: "1.4.45" + version: "1.4.46" firebase_remote_config_web: dependency: transitive description: name: firebase_remote_config_web - sha256: fc8ec1bfab20eec37026c4ca0272f3647eae28bc186168e4a2d2859ed7d0f863 + sha256: "6f018a0d7ce4b67784d29ec7193806e494adc243a4c880a4fab49e59afca212f" url: "https://pub.dev" source: hosted - version: "1.7.3" + version: "1.7.4" fixnum: dependency: transitive description: @@ -430,46 +430,70 @@ packages: url: "https://pub.dev" source: hosted version: "3.4.1" - flutter_custom_tabs: + flutter_inappwebview: dependency: "direct main" description: - name: flutter_custom_tabs - sha256: "34167bd15fa3479855c011f868e0789c9569c12b64358ca7250accc5a24c3312" + name: flutter_inappwebview + sha256: "80092d13d3e29b6227e25b67973c67c7210bd5e35c4b747ca908e31eb71a46d5" url: "https://pub.dev" source: hosted - version: "2.1.0" - flutter_custom_tabs_android: + version: "6.1.5" + flutter_inappwebview_android: dependency: transitive description: - name: flutter_custom_tabs_android - sha256: cf06fde8c002f326dc6cbf69ee3f97c3feead4436229da02d2e2aa39d5a5dbf4 + name: flutter_inappwebview_android + sha256: "62557c15a5c2db5d195cb3892aab74fcaec266d7b86d59a6f0027abd672cddba" url: "https://pub.dev" source: hosted - version: "2.1.0" - flutter_custom_tabs_ios: + version: "1.1.3" + flutter_inappwebview_internal_annotations: dependency: transitive description: - name: flutter_custom_tabs_ios - sha256: ef2de533bc45fb84fefc3854bc8b1e43001671c6bc6bc55faa57942eecd3f70a + name: flutter_inappwebview_internal_annotations + sha256: "5f80fd30e208ddded7dbbcd0d569e7995f9f63d45ea3f548d8dd4c0b473fb4c8" url: "https://pub.dev" source: hosted - version: "2.1.0" - flutter_custom_tabs_platform_interface: + version: "1.1.1" + flutter_inappwebview_ios: dependency: transitive description: - name: flutter_custom_tabs_platform_interface - sha256: e18e9b08f92582123bdb84fb6e4c91804b0579700fed6f887d32fd9a1e96a5d5 + name: flutter_inappwebview_ios + sha256: "5818cf9b26cf0cbb0f62ff50772217d41ea8d3d9cc00279c45f8aabaa1b4025d" url: "https://pub.dev" source: hosted - version: "2.1.0" - flutter_custom_tabs_web: + version: "1.1.2" + flutter_inappwebview_macos: dependency: transitive description: - name: flutter_custom_tabs_web - sha256: "08ae322b11e1972a233d057542279873d0f9d1d5f8159c2c741457239d9d562c" + name: flutter_inappwebview_macos + sha256: c1fbb86af1a3738e3541364d7d1866315ffb0468a1a77e34198c9be571287da1 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "1.1.2" + flutter_inappwebview_platform_interface: + dependency: transitive + description: + name: flutter_inappwebview_platform_interface + sha256: cf5323e194096b6ede7a1ca808c3e0a078e4b33cc3f6338977d75b4024ba2500 + url: "https://pub.dev" + source: hosted + version: "1.3.0+1" + flutter_inappwebview_web: + dependency: transitive + description: + name: flutter_inappwebview_web + sha256: "55f89c83b0a0d3b7893306b3bb545ba4770a4df018204917148ebb42dc14a598" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + flutter_inappwebview_windows: + dependency: transitive + description: + name: flutter_inappwebview_windows + sha256: "8b4d3a46078a2cdc636c4a3d10d10f2a16882f6be607962dbfff8874d1642055" + url: "https://pub.dev" + source: hosted + version: "0.6.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -649,10 +673,10 @@ packages: dependency: "direct main" description: name: google_maps_flutter - sha256: "2e302fa3aaf4e2a297f0342d83ebc5e8e9f826e9a716aef473fe7f404ec630a7" + sha256: "209856c8e5571626afba7182cf634b2910069dc567954e76ec3e3fb37f5e9db3" url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" google_maps_flutter_android: dependency: transitive description: @@ -833,10 +857,10 @@ packages: dependency: "direct main" description: name: logger - sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" + sha256: be4b23575aac7ebf01f225a241eb7f6b5641eeaf43c6a8613510fc2f8cf187d1 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.0" logging: dependency: transitive description: @@ -1430,10 +1454,10 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: @@ -1546,22 +1570,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" - webview_flutter: - dependency: "direct main" - description: - name: webview_flutter - sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec" - url: "https://pub.dev" - source: hosted - version: "4.10.0" webview_flutter_android: dependency: "direct dev" description: name: webview_flutter_android - sha256: dec83a8da0a2dcd8a25418534cc59348dbc2855fa1dd0cc929c62b6029fde392 + sha256: "86c2d01c37c4578ee46560109cf2e18fb271f0d080a796f09188d0952352e057" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" webview_flutter_platform_interface: dependency: transitive description: @@ -1570,14 +1586,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.10.0" - webview_flutter_wkwebview: - dependency: transitive - description: - name: webview_flutter_wkwebview - sha256: f14ee08021772fed913da8daebcfdeb46be457081e521e93e9918fe6cd1ce9e8 - url: "https://pub.dev" - source: hosted - version: "3.16.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 85cebb212..f7dbdeb9a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: The 4th generation of ÉTSMobile, the main gateway between the Éco # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 4.55.0 +version: 4.55.2 environment: sdk: '>=3.3.0 <4.0.0' @@ -28,12 +28,11 @@ dependencies: firebase_remote_config: ^5.1.3 # Web + flutter_inappwebview: ^6.1.5 url_launcher: ^6.3.1 - webview_flutter: ^4.10.0 - flutter_custom_tabs: ^2.1.0 # Utils - logger: ^2.4.0 + logger: ^2.5.0 fluttertoast: ^8.2.8 # Widgets @@ -47,7 +46,7 @@ dependencies: skeletonizer: ^1.4.2 # Google - google_maps_flutter: ^2.9.0 + google_maps_flutter: ^2.10.0 shared_preferences: ^2.3.3 flutter_markdown: ^0.7.4+1 @@ -67,7 +66,7 @@ dependencies: auto_size_text: ^3.0.0 easter_egg_trigger: ^1.0.1 timeago: ^3.7.0 - calendar_view: ^1.2.0 + calendar_view: 1.2.0 carousel_slider: ^5.0.0 reorderable_grid_view: ^2.2.8 infinite_scroll_pagination: ^4.1.0 diff --git a/test/features/app/integration/mocks/launch_url_service_mock.dart b/test/features/app/integration/mocks/launch_url_service_mock.dart index f323a6d29..65c79eb4a 100644 --- a/test/features/app/integration/mocks/launch_url_service_mock.dart +++ b/test/features/app/integration/mocks/launch_url_service_mock.dart @@ -1,6 +1,5 @@ // Package imports: import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; // Project imports: import 'package:notredame/features/app/integration/launch_url_service.dart'; @@ -8,14 +7,4 @@ import 'launch_url_service_mock.mocks.dart'; /// Mock for the [LaunchUrlService] @GenerateNiceMocks([MockSpec()]) -class LaunchUrlServiceMock extends MockLaunchUrlService { - static void stubCanLaunchUrl(LaunchUrlServiceMock client, String url, - {bool toReturn = true}) { - when(client.canLaunch(url)).thenAnswer((_) async => toReturn); - } - - static void stubLaunchUrl(LaunchUrlServiceMock client, String url, - {bool toReturn = true}) { - when(client.launch(url)).thenAnswer((_) async => toReturn); - } -} +class LaunchUrlServiceMock extends MockLaunchUrlService {} diff --git a/test/features/app/widgets/bottom_bar_test.dart b/test/features/app/widgets/bottom_bar_test.dart index bc2df6fcd..08149c4aa 100644 --- a/test/features/app/widgets/bottom_bar_test.dart +++ b/test/features/app/widgets/bottom_bar_test.dart @@ -9,7 +9,7 @@ import 'package:mockito/mockito.dart'; import 'package:notredame/features/app/integration/networking_service.dart'; import 'package:notredame/features/app/navigation/navigation_service.dart'; import 'package:notredame/features/app/navigation/router_paths.dart'; -import 'package:notredame/features/app/widgets/bottom_bar.dart'; +import 'package:notredame/features/app/widgets/navigation/bottom_bar.dart'; import '../../../common/helpers.dart'; import '../analytics/mocks/analytics_service_mock.dart'; import '../navigation/mocks/navigation_service_mock.dart'; @@ -68,7 +68,7 @@ void main() { await tester.pumpAndSettle(); await tester.tap(find.byIcon(Icons.schedule_outlined)); - await tester.tap(find.byIcon(Icons.dashboard_outlined)); + await tester.tap(find.byIcon(Icons.dashboard)); verify(navigationServiceMock.pushNamedAndRemoveDuplicates(RouterPaths.dashboard)); }); diff --git a/test/features/app/widgets/link_web_view_test.dart b/test/features/app/widgets/link_web_view_test.dart deleted file mode 100644 index a2447d963..000000000 --- a/test/features/app/widgets/link_web_view_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -// Flutter imports: -import 'package:flutter/material.dart'; - -// Package imports: -import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:webview_flutter_android/webview_flutter_android.dart'; - -// Project imports: -import 'package:notredame/features/app/widgets/link_web_view.dart'; -import 'package:notredame/features/ets/quick-link/models/quick_link.dart'; -import '../../../common/helpers.dart'; - -final _quickLink = QuickLink( - id: 1, - image: const Icon(Icons.ac_unit), - name: 'test', - link: 'https://clubapplets.ca/'); - -void main() { - group('LinkWebView - ', () { - setUp(() { - WebViewPlatform.instance = AndroidWebViewPlatform(); - setupNetworkingServiceMock(); - }); - - testWidgets('has an AppBar and a WebView', (WidgetTester tester) async { - await tester.pumpWidget(localizedWidget(child: LinkWebView(_quickLink))); - - final appBar = find.byType(AppBar); - final webview = find.byType(WebViewWidget); - - expect(appBar, findsNWidgets(1)); - expect(_quickLink.name, 'test'); - expect(webview, findsNWidgets(1)); - }); - }); -} diff --git a/test/features/dashboard/dashboard_view_test.dart b/test/features/dashboard/dashboard_view_test.dart index e0ef8e71f..87975af32 100644 --- a/test/features/dashboard/dashboard_view_test.dart +++ b/test/features/dashboard/dashboard_view_test.dart @@ -173,6 +173,7 @@ void main() { courseRepositoryMock = setupCourseRepositoryMock(); setupNetworkingServiceMock(); setupAnalyticsServiceMock(); + setupLaunchUrlServiceMock(); setupPreferencesServiceMock(); // TODO: Remove when 4.50.1 is released SharedPreferences.setMockInitialValues({}); diff --git a/test/features/ets/quick-link/widgets/security-info/emergency_view_test.dart b/test/features/ets/quick-link/widgets/security-info/emergency_view_test.dart index b104cb83b..3843feb31 100644 --- a/test/features/ets/quick-link/widgets/security-info/emergency_view_test.dart +++ b/test/features/ets/quick-link/widgets/security-info/emergency_view_test.dart @@ -4,8 +4,6 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:webview_flutter/webview_flutter.dart'; -import 'package:webview_flutter_android/webview_flutter_android.dart'; // Project imports: import 'package:notredame/features/ets/quick-link/widgets/security-info/emergency_view.dart'; @@ -14,8 +12,8 @@ import '../../../../../common/helpers.dart'; void main() { group('EmergencyView - ', () { setUp(() async { - WebViewPlatform.instance = AndroidWebViewPlatform(); setupNetworkingServiceMock(); + setupLaunchUrlServiceMock(); }); tearDown(() {}); diff --git a/test/features/ets/quick-link/widgets/security-info/security_view_test.dart b/test/features/ets/quick-link/widgets/security-info/security_view_test.dart index 6ff3db4eb..00f3e9786 100644 --- a/test/features/ets/quick-link/widgets/security-info/security_view_test.dart +++ b/test/features/ets/quick-link/widgets/security-info/security_view_test.dart @@ -18,6 +18,7 @@ void main() { setUp(() async { intl = await setupAppIntl(); setupNetworkingServiceMock(); + setupLaunchUrlServiceMock(); }); tearDown(() {}); diff --git a/test/features/ets/quick-link/widgets/web_link_card_viewmodel_test.dart b/test/features/ets/quick-link/widgets/web_link_card_viewmodel_test.dart index bbd4f8335..d2f592219 100644 --- a/test/features/ets/quick-link/widgets/web_link_card_viewmodel_test.dart +++ b/test/features/ets/quick-link/widgets/web_link_card_viewmodel_test.dart @@ -58,7 +58,7 @@ void main() { group('onLinkClicked -', () { test('navigate to security', () async { - await viewModel.onLinkClicked(securityQuickLink, Brightness.light); + await viewModel.onLinkClicked(securityQuickLink); verify(analyticsServiceMock.logEvent( "QuickLink", "QuickLink clicked: test")); @@ -70,10 +70,9 @@ void main() { InternalInfoServiceMock.stubGetDeviceInfoForErrorReporting( internalInfoServiceMock); - await viewModel.onLinkClicked(quickLink, Brightness.light); + await viewModel.onLinkClicked(quickLink); - verify(launchUrlServiceMock.launchInBrowser( - quickLink.link, Brightness.light)); + verify(launchUrlServiceMock.launchInBrowser(quickLink.link)); verifyNoMoreInteractions(navigationServiceMock); }); }); diff --git a/test/features/more/about/about_view_test.dart b/test/features/more/about/about_view_test.dart index 23d376d1a..fb5222c3e 100644 --- a/test/features/more/about/about_view_test.dart +++ b/test/features/more/about/about_view_test.dart @@ -11,7 +11,9 @@ import '../../../common/helpers.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('AboutView - ', () { - setUp(() async {}); + setUp(() async { + setupLaunchUrlServiceMock(); + }); tearDown(() {}); diff --git a/test/features/more/faq/faq_viewmodel_test.dart b/test/features/more/faq/faq_viewmodel_test.dart index 80284e733..b2add4ca2 100644 --- a/test/features/more/faq/faq_viewmodel_test.dart +++ b/test/features/more/faq/faq_viewmodel_test.dart @@ -1,6 +1,3 @@ -// Flutter imports: -import 'package:flutter/material.dart'; - // Package imports: import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; @@ -30,20 +27,11 @@ void main() { unregister(); }); - group('Emails - ', () { - test('Has the right mailto', () { - final str = viewModel.mailtoStr("email", "subject"); - - expect(str, "mailto:email?subject=subject"); - }); - }); - group('Webview - ', () { test('Calls launchInBrowser', () { - viewModel.launchWebsite("https://clubapplets.ca/", Brightness.light); + viewModel.launchWebsite("https://clubapplets.ca/"); - verify(launchUrlServiceMock.launchInBrowser( - "https://clubapplets.ca/", Brightness.light)) + verify(launchUrlServiceMock.launchInBrowser("https://clubapplets.ca/")) .called(1); }); }); diff --git a/test/features/more/feedback/models/feedback_view_test.dart b/test/features/more/feedback/models/feedback_view_test.dart index fa7b4821d..376f05010 100644 --- a/test/features/more/feedback/models/feedback_view_test.dart +++ b/test/features/more/feedback/models/feedback_view_test.dart @@ -13,6 +13,7 @@ void main() { setUp(() async { await setupAppIntl(); setupNavigationServiceMock(); + setupLaunchUrlServiceMock(); setupPreferencesServiceMock(); setupGithubApiMock(); }); diff --git a/test/features/more/more_view_test.dart b/test/features/more/more_view_test.dart index 5b82751ef..55e28031f 100644 --- a/test/features/more/more_view_test.dart +++ b/test/features/more/more_view_test.dart @@ -33,6 +33,7 @@ void main() { setupUserRepositoryMock(); setupCacheManagerMock(); setupGithubApiMock(); + setupLaunchUrlServiceMock(); setupNetworkingServiceMock(); setupAnalyticsServiceMock(); setupFlutterToastMock();